Table of Contents
Dealing with Cookies has been a typical requirement of most web developers since the early days of the World Wide Web. In this article, after a brief introduction to explain how Cookies work in a typical web application, we will present some helper classes that allow you to implement the main activities necessary to manage Cookies in any ASP.NET project - Web Forms, MVC, and/or Core - in a rather simple way: we're talking about reading, writing, editing and deleting functions, cookie key-value pairs management, and other useful features such as expiration date, domain restrictions, HttpOnly mode, Secure flag, and so on. In the last paragraph we will deal with how to configure the same settings and features at a global level, or rather in relation to all the cookies created by our web application, using the appropriate web.config file.
Introduction
Cookies perform a series of very important functions in most web applications. The most common cookie usage is the one involved in the user authentication phase using HTTP forms: authentication cookies are created by the server on the user's computer at the time of login and contain the login that which can then be used - by subsequent visits - to detect a previous (successful) auth attempt and retrieve the required info to automatically perform the login. This data, depending on the case, may contain more or less delicate information, such as a hash of username and password, a timestamped authentication token and so on. For this reason it is extremely important to protect them from unauthorized access by following a series of standard security techniques, such as:
- data encryption, or the encryption of personal data and values potentially subject to attack (username, password)
- hashing of any password or security key that is not essential to transmit in clear text.
- domain restrictions, by limiting the reading / writing permissions to a specific domain or sub-domain.
- set the expiration date, in order to prevent the cookie from remaining stored indefinitely within the browser cache.
- prevent access of client-side scripts trough the HttpOnly property: if enabled, this feature will ask the browser to hide the cookie from JavaScript and other client-side languages present on the page.
- restrict the cookie activities to HTTPS connections only over secure SSL / TLS channels through the Secure flag; if enabled, this feature will prevent the cookie from being read and/or written from any eavesdropper that could intercept the communication flow between the client (browser) and the server (web application), thus mitigating most man-in-the-middle attacks and other common tampering-based threats.
Create a Cookie
The following static method, written in C# language, shows how to create a cookie on the user's browser:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
/// <summary> /// Stores a value in a user Cookie, creating it if it doesn't exists yet. /// </summary> /// <param name="cookieName">Cookie name</param> /// <param name="cookieDomain">Cookie domain (or NULL to use default domain value)</param> /// <param name="keyName">Cookie key name (if the cookie is a keyvalue pair): if NULL or EMPTY, the cookie will be treated as a single variable.</param> /// <param name="value">Value to store into the cookie</param> /// <param name="expirationDate">Expiration Date (set it to NULL to leave default expiration date)</param> /// <param name="httpOnly">set it to TRUE to enable HttpOnly, FALSE otherwise (default: false)</param> /// <param name="sameSite">set it to 'None', 'Lax', 'Strict' or '(-1)' to not add it (default: '(-1)').</param> /// <param name="secure">set it to TRUE to enable Secure (HTTPS only), FALSE otherwise</param> public static void StoreInCookie( string cookieName, string cookieDomain, string keyName, string value, DateTime? expirationDate, bool httpOnly = false, SameSiteMode sameSite = (SameSiteMode)(-1), bool secure = false) { // NOTE: we have to look first in the response, and then in the request. // This is required when we update multiple keys inside the cookie. HttpCookie cookie = HttpContext.Current.Response.Cookies.AllKeys.Contains(cookieName) ? HttpContext.Current.Response.Cookies[cookieName] : HttpContext.Current.Request.Cookies[cookieName]; if (cookie == null) cookie = new HttpCookie(cookieName); if (!String.IsNullOrEmpty(keyName)) cookie.Values.Set(keyName, value); else cookie.Value = value; if (expirationDate.HasValue) cookie.Expires = expirationDate.Value; if (!String.IsNullOrEmpty(cookieDomain)) cookie.Domain = cookieDomain; if (httpOnly) cookie.HttpOnly = true; cookie.Secure = secure; cookie.SameSite = sameSite; HttpContext.Current.Response.Cookies.Set(cookie); } |
As you can see by analyzing the parameters of the method, you can specify the cookie name, domain, expiration date and HttpOnly property: the Secure flag can also be set globally within the web.config file, as we'll seen later on, in order to make it globally enabled (or disabled) for all cookies generated by the site.
Create a Cookie with multiple values (using key-value pairs)
Each cookie represents, by definition, a single key-value pair: the key is the name of the cookie, while the value is just a plain, unencrypted, text string. This means that, under normal conditions, to pass multiple information - such as username, password and login date - you will need to create multiple cookies. Such way of proceeding might seem inefficient, especially if these values are linked together and there's no need to grant the application independent access to each one of them: in such scenarios, it's possible to use one of the many existing serialization standards to use a single cookie as a container of multiple key-value pairs, or a set of key-values.
The following implementation, written in C#language, takes care of that by using a standard Dictionary of strings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/// <summary> /// Stores multiple values in a Cookie using a key-value dictionary, creating the cookie (and/or the key) if it doesn't exists yet. /// </summary> /// <param name="cookieName">Cookie name</param> /// <param name="cookieDomain">Cookie domain (or NULL to use default domain value)</param> /// <param name="keyName">Cookie key name (if the cookie is a keyvalue pair): if NULL or EMPTY, this method will raise an exception since it's required when inserting multiple values.</param> /// <param name="values">Values to store into the cookie</param> /// <param name="expirationDate">Expiration Date (set it to NULL to leave default expiration date)</param> /// <param name="httpOnly">set it to TRUE to enable HttpOnly, FALSE otherwise (default: false)</param> /// <param name="sameSite">set it to 'None', 'Lax', 'Strict' or '(-1)' to not add it (default: '(-1)').</param> /// <param name="secure">set it to TRUE to enable Secure (HTTPS only), FALSE otherwise</param> public static void StoreInCookie( string cookieName, string cookieDomain, Dictionary<string, string> keyValueDictionary, DateTime? expirationDate, bool httpOnly = false, SameSiteMode sameSite = (SameSiteMode)(-1), bool secure = false) { // NOTE: we have to look first in the response, and then in the request. // This is required when we update multiple keys inside the cookie. HttpCookie cookie = HttpContext.Current.Response.Cookies.AllKeys.Contains(cookieName) ? HttpContext.Current.Response.Cookies[cookieName] : HttpContext.Current.Request.Cookies[cookieName]; if (cookie == null) cookie = new HttpCookie(cookieName); if (keyValueDictionary == null || keyValueDictionary.Count == 0) cookie.Value = null; else foreach (var kvp in keyValueDictionary) cookie.Values.Set(kvp.Key, kvp.Value); if (expirationDate.HasValue) cookie.Expires = expirationDate.Value; if (!String.IsNullOrEmpty(cookieDomain)) cookie.Domain = cookieDomain; if (httpOnly) cookie.HttpOnly = true; cookie.Secure = secure; cookie.SameSite = sameSite; HttpContext.Current.Response.Cookies.Set(cookie); } |
As we can see, the dictionary is serialized within the native Values property of the HttpCookie class provided by ASP.NET.
Such technique makes possible to store real objects inside the cookie, provided that they their properties can be serialized into strings. At the same time, it is good not to overdo this approach, since the process of serialization and deserialization of cookies is certainly less efficient than the use of the same provided by the various specifications that define their operation (from the US5774670, published in 1998, to the RFC6265 currently in force).
Read a Cookie
The following static method, also written in C #, allows you to retrieve a string corresponding to the value of a single Cookie or - if we're dealing with a key-value Cookie - to a single key within a single Cookie.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// <summary> /// Retrieves a single value from Request.Cookies /// </summary> public static string GetFromCookie(string cookieName, string keyName) { HttpCookie cookie = HttpContext.Current.Request.Cookies[cookieName]; if (cookie != null) { string val = (!String.IsNullOrEmpty(keyName)) ? cookie[keyName] : cookie.Value; if (!String.IsNullOrEmpty(val)) return Uri.UnescapeDataString(val); } return null; } |
Delete a Cookie
The following static C # method allows you to delete a cookie, or - if we're dealing with a key-value Cookie - a single key from an existing cookie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/// <summary> /// Removes a single value from a cookie or the whole cookie (if keyName is null) /// </summary> /// <param name="cookieName">Cookie name to remove (or to remove a KeyValue in)</param> /// <param name="keyName">the name of the key value to remove. If NULL or EMPTY, the whole cookie will be removed.</param> /// <param name="domain">cookie domain (required if you need to delete a .domain.it type of cookie)</param> public static void RemoveCookie(string cookieName, string keyName, string domain) { if (HttpContext.Current.Request.Cookies[cookieName] != null) { HttpCookie cookie = HttpContext.Current.Request.Cookies[cookieName]; // SameSite.None Cookies won't be accepted by Google Chrome and other modern browsers if they're not secure, which would lead in a "non-deletion" bug. // in this specific scenario, we need to avoid emitting the SameSite attribute to ensure that the cookie will be deleted. if (cookie.SameSite == SameSiteMode.None && !cookie.Secure) cookie.SameSite = (SameSiteMode)(-1); if (String.IsNullOrEmpty(keyName)) { cookie.Expires = DateTime.UtcNow.AddYears(-1); if (!String.IsNullOrEmpty(domain)) cookie.Domain = domain; HttpContext.Current.Response.Cookies.Add(cookie); HttpContext.Current.Request.Cookies.Remove(cookieName); } else { cookie.Values.Remove(keyName); if (!String.IsNullOrEmpty(domain)) cookie.Domain = domain; HttpContext.Current.Response.Cookies.Add(cookie); } } } |
Check for Cookie existence
The last method we're sharing can be useful to determine whether a cookie (or a given key-value pair within it) exists or not, without modifying it in any way.
1 2 3 4 5 6 7 8 9 10 |
/// <summary> /// Checks if a cookie / key exists in the current HttpContext. /// </summary> public static bool CookieExist(string cookieName, string keyName) { HttpCookieCollection cookies = HttpContext.Current.Request.Cookies; return (String.IsNullOrEmpty(keyName)) ? cookies[cookieName] != null : cookies[cookieName] != null && cookies[cookieName][keyName] != null; } |
Web Application Configuration
Now that we have seen how we can manage Cookies at the code level, it is time to take a look at the web.config file to understand how we can globally set some characteristics that will impact all the Cookies that will be created, modified or otherwise managed by our web application.
Secure Flag (requireSSL)
If we have configured our site to only accept web requests from protected channels (HTTPS with SSL / TLS certificate) it is certainly worth setting the Secure flag as active: before doing so, however, it is advisable to spend a couple of minutes to understand how it works.
The Secure flag, if present, instructs the browser to authorize the create feature (and in the most recent versions of browsers, even the read feature) of all our website's cookies only if a secure connection has been estabilished. Unfortunately, most older browsers will allow the reading of Secure cookies even through unprotected connections, which is undoubtely a security issue: however, such behaviour can be strongly mitigated at the server level by allowing only protected connections and refusing (or redirecting via special HTTP rewrite / redirect rules to HTTPS) those coming from unsafe channels; anyway, using the Secure flag is still a great deal since it will help us to shield our "precious" authentication cookies from tampering attacks and/or other malicious activities performed by a potential man-in-the-middle ( MITM), preventing the cookie from being taken over or altered in any way.
To set the Secure flag globally, enter the following configuration parameters in the web.config file:
1 2 3 4 5 6 |
<configuration> <system.web> <!-- Force secure connections for all Cookies --> <httpCookies requireSSL="true" /> </system.web> </configuration> |
If our web application is using Forms Authentication or other form-based authentications, the Secure flag should also be set in the configuration part of the aforementioned forms in order to prevent the authentication cookies from inheriting the settings (by default not secure) of the Form Authentication feature. To handle such scenarios, enter the following configuration parameters in the web.config file in addition to the previous ones:
1 2 3 4 5 6 7 8 |
<configuration> <system.web> <authentication mode="Forms"> <!-- Force secure connections for Forms Authentication --> <forms requireSSL="true" /> </authentication> </system.web> </configuration> |
HttpOnly Flag
As already explained above, the HttpOnly flag - which is not active by default - tells the browser to hide Cookies from all client-side scripts, such as JavaScript. To force the activation of this flag at global level - which means, for all cookies created by our web application - you need to configure the httpOnly = "true" attribute in the web.config file in the following way:
1 2 3 4 5 6 |
<configuration> <system.web> <!-- Prevent client-side scripts from reading Cookies --> <httpCookies httpOnlyCookies="true" /> </system.web> </configuration> |
Domain restrictions
The domain restrictions, which we have already discussed above, can also be set globally by configuring the web.config file as follows:
1 2 3 4 5 6 |
<configuration> <system.web> <!-- Prevent access to cookies from any external domain --> <httpCookies domain=".mywebsite.com" /> </system.web> </configuration> |
Conclusion
That's it, at least for the time being: we hope that this simple guide to ASP.NET Cookies will be useful to all developers and system administrators called upon to tackle these problems. Happy development!
This is an awesome post.Really very informative and creative contents. These concept is a good way to enhance the knowledge.I like it and help me to development very well.Thank you for this brief explanation and very nice information.Well, got a good knowledge.
This is never going to be null:
HttpContext.Current.Response.Cookies[cookieName] ?? HttpContext.Current.Request.Cookies[cookieName]
Reading Response.Cookies when they don’t exist works as adding cookie!
You’re right, we’ve fixed that a while ago but we forgot to update the source code in this post accordingly: we’ve just done that. The new code also deals with the new
SameSite
andSecure
properties (added in .NET Framework 4.7.2) and the recent ASP.NET Framework updates. Thanks!Thank you for sharing detail post about manage cookies in the server side.
I have an issue to delete the cookie for Google Chrome only. The above code worked perfectly in Internet Explorer and Firefox but unfortunately Google Chrome is not deleting the cookie.
I am using Chrome Version 85.0.4183.121 (Official Build) (64-bit) and .Net Fra,ework 4.5 above code without SameSite and Secure attributes.
I appreciate your help if you guide me to resolve the issue.
Looking forward for your help.
Regards,
Imdadhusen
Try to update the framework to 4.7.2 and try again with my sample: if they don’t work, try to understand how to properly set the cookie attributes depending on various thing (HTTP protocol, secure, http-only, samesite, etc.).
Also, I suggest you to carefully read this post for additional info:
https://techcommunity.microsoft.com/t5/iis-support-blog/samesite-cookie-updates-in-asp-net-or-how-the-net-framework-from/ba-p/1156246
Ryan,
in Delete cookie, why you have to add a Response cookie, then remove request cookie?
HttpContext.Current.Response.Cookies.Add(cookie);
HttpContext.Current.Request.Cookies.Remove(cookieName);
Remove request cookie = to ensure it won’t be find anymore within that request for the rest of the server-side request lifecycle (such as subsequent checks for cookie presence).
Add response cookie = to ensure it will be deleted by the client/browser.