HTTP Cookies in ASP.NET Web API
by Mike Wasson
This topic describes how to send and receive HTTP cookies in Web API.
Background on HTTP Cookies
This section gives a brief overview of how cookies are implemented at the HTTP level. For details, consult RFC 6265.
A cookie is a piece of data that a server sends in the HTTP response. The client (optionally) stores the cookie and returns it on subsequet requests. This allows the client and server to share state. To set a cookie, the server includes a Set-Cookie header in the response. The format of a cookie is a name-value pair, with optional attributes. For example:
[!code-powershellMain]
1: Set-Cookie: session-id=1234567
Here is an example with attributes:
[!code-powershellMain]
1: Set-Cookie: session-id=1234567; max-age=86400; domain=example.com; path=/;
To return a cookie to the server, the client inclues a Cookie header in later requests.
[!code-consoleMain]
1: Cookie: session-id=1234567
An HTTP response can include multiple Set-Cookie headers.
[!code-powershellMain]
1: Set-Cookie: session-token=abcdef;
2: Set-Cookie: session-id=1234567;
The client returns multiple cookies using a single Cookie header.
[!code-consoleMain]
1: Cookie: session-id=1234567; session-token=abcdef;
The scope and duration of a cookie are controlled by following attributes in the Set-Cookie header:
- Domain: Tells the client which domain should receive the cookie. For example, if the domain is “example.com”, the client returns the cookie to every subdomain of example.com. If not specified, the domain is the origin server.
- Path: Restricts the cookie to the specified path within the domain. If not specified, the path of the request URI is used.
- Expires: Sets an expiration date for the cookie. The client deletes the cookie when it expires.
- Max-Age: Sets the maximum age for the cookie. The client deletes the cookie when it reaches the maximum age.
If both Expires
and Max-Age
are set, Max-Age
takes precedence. If neither is set, the client deletes the cookie when the current session ends. (The exact meaning of “session” is determined by the user-agent.)
However, be aware that clients may ignore cookies. For example, a user might disable cookies for privacy reasons. Clients may delete cookies before they expire, or limit the number of cookies stored. For privacy reasons, clients often reject “third party” cookies, where the domain does not match the origin server. In short, the server should not rely on getting back the cookies that it sets.
Cookies in Web API
To add a cookie to an HTTP response, create a CookieHeaderValue instance that represents the cookie. Then call the AddCookies extension method, which is defined in the System.Net.Http. HttpResponseHeadersExtensions class, to add the cookie.
For example, the following code adds a cookie within a controller action:
[!code-csharpMain]
1: public HttpResponseMessage Get()
2: {
3: var resp = new HttpResponseMessage();
4:
5: var cookie = new CookieHeaderValue("session-id", "12345");
6: cookie.Expires = DateTimeOffset.Now.AddDays(1);
7: cookie.Domain = Request.RequestUri.Host;
8: cookie.Path = "/";
9:
10: resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
11: return resp;
12: }
Notice that AddCookies takes an array of CookieHeaderValue instances.
To extract the cookies from a client request, call the GetCookies method:
[!code-csharpMain]
1: string sessionId = "";
2:
3: CookieHeaderValue cookie = Request.Headers.GetCookies("session-id").FirstOrDefault();
4: if (cookie != null)
5: {
6: sessionId = cookie["session-id"].Value;
7: }
A CookieHeaderValue contains a collection of CookieState instances. Each CookieState represents one cookie. Use the indexer method to get a CookieState by name, as shown.
Structured Cookie Data
Many browsers limit how many cookies they will store—both the total number, and the number per domain. Therefore, it can be useful to put structured data into a single cookie, instead of setting multiple cookies.
[!NOTE] RFC 6265 does not define the structure of cookie data.
Using the CookieHeaderValue class, you can pass a list of name-value pairs for the cookie data. These name-value pairs are encoded as URL-encoded form data in the Set-Cookie header:
[!code-csharpMain]
1: var resp = new HttpResponseMessage();
2:
3: var nv = new NameValueCollection();
4: nv["sid"] = "12345";
5: nv["token"] = "abcdef";
6: nv["theme"] = "dark blue";
7: var cookie = new CookieHeaderValue("session", nv);
8:
9: resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
The previous code produces the following Set-Cookie header:
[!code-powershellMain]
1: Set-Cookie: session=sid=12345&token=abcdef&theme=dark+blue;
The CookieState class provides an indexer method to read the sub-values from a cookie in the request message:
[!code-csharpMain]
1: string sessionId = "";
2: string sessionToken = "";
3: string theme = "";
4:
5: CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault();
6: if (cookie != null)
7: {
8: CookieState cookieState = cookie["session"];
9:
10: sessionId = cookieState["sid"];
11: sessionToken = cookieState["token"];
12: theme = cookieState["theme"];
13: }
Example: Set and Retrieve Cookies in a Message Handler
The previous examples showed how to use cookies from within a Web API controller. Another option is to use message handlers. Message handlers are invoked earlier in the pipeline than controllers. A message handler can read cookies from the request before the request reaches the controller, or add cookies to the response after the controller generates the response.
The following code shows a message handler for creating session IDs. The session ID is stored in a cookie. The handler checks the request for the session cookie. If the request does not include the cookie, the handler generates a new session ID. In either case, the handler stores the session ID in the HttpRequestMessage.Properties property bag. It also adds the session cookie to the HTTP response.
This implementation does not validate that the session ID from the client was actually issued by the server. Don’t use it as a form of authentication! The point of the example is to show HTTP cookie management.
[!code-csharpMain]
1: using System;
2: using System.Linq;
3: using System.Net;
4: using System.Net.Http;
5: using System.Net.Http.Headers;
6: using System.Threading;
7: using System.Threading.Tasks;
8: using System.Web.Http;
9:
10: public class SessionIdHandler : DelegatingHandler
11: {
12: public static string SessionIdToken = "session-id";
13:
14: async protected override Task<HttpResponseMessage> SendAsync(
15: HttpRequestMessage request, CancellationToken cancellationToken)
16: {
17: string sessionId;
18:
19: // Try to get the session ID from the request; otherwise create a new ID.
20: var cookie = request.Headers.GetCookies(SessionIdToken).FirstOrDefault();
21: if (cookie == null)
22: {
23: sessionId = Guid.NewGuid().ToString();
24: }
25: else
26: {
27: sessionId = cookie[SessionIdToken].Value;
28: try
29: {
30: Guid guid = Guid.Parse(sessionId);
31: }
32: catch (FormatException)
33: {
34: // Bad session ID. Create a new one.
35: sessionId = Guid.NewGuid().ToString();
36: }
37: }
38:
39: // Store the session ID in the request property bag.
40: request.Properties[SessionIdToken] = sessionId;
41:
42: // Continue processing the HTTP request.
43: HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
44:
45: // Set the session ID as a cookie in the response message.
46: response.Headers.AddCookies(new CookieHeaderValue[] {
47: new CookieHeaderValue(SessionIdToken, sessionId)
48: });
49:
50: return response;
51: }
52: }
A controller can get the session ID from the HttpRequestMessage.Properties property bag.
[!code-csharpMain]
1: public HttpResponseMessage Get()
2: {
3: string sessionId = Request.Properties[SessionIdHandler.SessionIdToken] as string;
4:
5: return new HttpResponseMessage()
6: {
7: Content = new StringContent("Your session ID = " + sessionId)
8: };
9: }
|