Cookies HTTP en ASP.NET Web API

Este tema describe cómo enviar y recibir cookies HTTP en Web API.

Información general sobre las cookies HTTP

Esta sección ofrece una breve introducción sobre cómo se implementan las cookies a nivel de HTTP. Para más detalles, consulte RFC 6265.

Una cookie es un dato que un servidor envía en la respuesta HTTP. El cliente (opcionalmente) almacena la cookie y la devuelve en posteriores solicitudes. Esto permite que el cliente y el servidor compartan estado. Para establecer una cookie, el servidor incluye un encabezado Set-Cookie en la respuesta. El formato de una cookie es un par nombre-valor, con atributos opcionales. Por ejemplo:

Set-Cookie: session-id=1234567

Este es un ejemplo con atributos:

Set-Cookie: session-id=1234567; max-age=86400; domain=example.com; path=/;

Para devolver una cookie al servidor, el cliente incluye un encabezado Cookie en las solicitudes posteriores.

Cookie: session-id=1234567

Diagram of process to return a cookie to the server, during which the client includes a Cookie header in later requests.

Una respuesta HTTP puede incluir varios encabezados Set-Cookie.

Set-Cookie: session-token=abcdef;
Set-Cookie: session-id=1234567;

El cliente devuelve varias cookies usando un único encabezado Cookie.

Cookie: session-id=1234567; session-token=abcdef;

El ámbito y la duración de una cookie se controlan mediante los siguientes atributos del encabezado Set-Cookie:

  • Domain: indica al cliente qué dominio debe recibir la cookie. Por ejemplo, si el dominio es "ejemplo.com", el cliente devuelve la cookie a cada subdominio de ejemplo.com. Si no se especifica, el dominio es el servidor de origen.
  • Path: restringe la cookie a la ruta de acceso especificada dentro del dominio. Si no se especifica, se usa la ruta del URI de la solicitud.
  • Expires: establece una fecha de expiración para la cookie. El cliente elimina la cookie cuando expira.
  • Max-Age: establece la antigüedad máxima de la cookie. El cliente elimina la cookie cuando alcanza la edad máxima.

Si se establecen tanto Expires como Max-Age, tiene preferencia Max-Age. Si no se establece ninguna de las dos, el cliente eliminará la cookie cuando finalice la sesión actual. (El significado exacto de "sesión" lo determina el agente de usuario).

Sin embargo, tenga en cuenta que los clientes pueden ignorar las cookies. Por ejemplo, un usuario puede deshabilitar las cookies por motivos de privacidad. Los clientes pueden eliminar las cookies antes de su expiración o limitar el número de cookies almacenadas. Por razones de privacidad, los clientes suelen rechazar las cookies de "terceros", cuyo dominio no coincide con el servidor de origen. En resumen, el servidor no debería depender de que se le devuelvan las cookies que él mismo establece.

Cookies en Web API

Para agregar una cookie a una respuesta HTTP, cree una instancia CookieHeaderValue que represente la cookie. Después, llame al método de extensión AddCookies, que está definido en el archivo System.Net.Http. clase HttpResponseHeadersExtensions, para agregar la cookie.

Por ejemplo, el siguiente código agrega una cookie dentro de una acción del controlador:

public HttpResponseMessage Get()
{
    var resp = new HttpResponseMessage();

    var cookie = new CookieHeaderValue("session-id", "12345");
    cookie.Expires = DateTimeOffset.Now.AddDays(1);
    cookie.Domain = Request.RequestUri.Host;
    cookie.Path = "/";

    resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
    return resp;
}

Observe que AddCookies toma una matriz de instancias de CookieHeaderValue.

Para extraer las cookies de una solicitud de cliente, llame al método GetCookies:

string sessionId = "";

CookieHeaderValue cookie = Request.Headers.GetCookies("session-id").FirstOrDefault();
if (cookie != null)
{
    sessionId = cookie["session-id"].Value;
}

Un CookieHeaderValue contiene una colección de instancias de CookieState. Cada CookieState representa una cookie. Use el método del indizador para obtener un CookieState por su nombre, como se muestra.

Muchos exploradores limitan el número de cookies que almacenarán, tanto el número total como el número por dominio. Por lo tanto, puede ser útil poner los datos estructurados en una sola cookie, en lugar de establecer varias cookies.

Nota:

RFC 6265 no define la estructura de los datos de cookies.

Con la clase CookieHeaderValue, puede pasar una lista de pares nombre-valor para los datos de cookies. Estos pares nombre-valor se codifican como datos de formulario codificados en URL en el encabezado Set-Cookie:

var resp = new HttpResponseMessage();

var nv = new NameValueCollection();
nv["sid"] = "12345";
nv["token"] = "abcdef";
nv["theme"] = "dark blue";
var cookie = new CookieHeaderValue("session", nv); 

resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });

El código anterior produce el siguiente encabezado Set-Cookie:

Set-Cookie: session=sid=12345&token=abcdef&theme=dark+blue;

La clase CookieState proporciona un método indizador para leer los subvalores de una cookie en el mensaje de solicitud:

string sessionId = "";
string sessionToken = "";
string theme = "";

CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault();
if (cookie != null)
{
    CookieState cookieState = cookie["session"];

    sessionId = cookieState["sid"];
    sessionToken = cookieState["token"];
    theme = cookieState["theme"];
}

Ejemplo: Establecer y recuperar cookies en un controlador de mensajes

Los ejemplos anteriores mostraban cómo usar cookies desde dentro de un controlador de Web API. Otra opción es usar controladores de mensajes. Los controladores de mensajes se invocan antes en la canalización que los controladores. Un controlador de mensajes puede leer cookies de la solicitud antes de que esta llegue al controlador, o agregar cookies a la respuesta después de que el controlador genere la respuesta.

Diagram of process to set and receive cookies in a message handler. Illustrates how message handlers are invoked earlier in pipeline than controllers.

El siguiente código muestra un controlador de mensajes para crear id. de sesión. El id. de sesión se almacena en una cookie. El controlador comprueba la solicitud de la cookie de sesión. Si la solicitud no incluye la cookie, el controlador genera un nuevo id. de sesión. En cualquier caso, el controlador almacena el id. de sesión en la bolsa de propiedades HttpRequestMessage.Properties. También agrega la cookie de sesión a la respuesta HTTP.

Esta implementación no valida que el id. de sesión del cliente haya sido realmente emitido por el servidor. ¡No lo use como forma de autentificación! El objetivo del ejemplo es mostrar la administración de cookies HTTP.

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

public class SessionIdHandler : DelegatingHandler
{
    public static string SessionIdToken = "session-id";

    async protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        string sessionId;

        // Try to get the session ID from the request; otherwise create a new ID.
        var cookie = request.Headers.GetCookies(SessionIdToken).FirstOrDefault();
        if (cookie == null)
        {
            sessionId = Guid.NewGuid().ToString();
        }
        else 
        {
            sessionId = cookie[SessionIdToken].Value;
            try
            {
                Guid guid = Guid.Parse(sessionId);
            }
            catch (FormatException)
            {
                // Bad session ID. Create a new one.
                sessionId = Guid.NewGuid().ToString();
            }
        }

        // Store the session ID in the request property bag.
        request.Properties[SessionIdToken] = sessionId;

        // Continue processing the HTTP request.
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        // Set the session ID as a cookie in the response message.
        response.Headers.AddCookies(new CookieHeaderValue[] {
            new CookieHeaderValue(SessionIdToken, sessionId) 
        });

        return response;
    }
}

Un controlador puede obtener el id. de sesión de la bolsa de propiedades HttpRequestMessage.Properties.

public HttpResponseMessage Get()
{
    string sessionId = Request.Properties[SessionIdHandler.SessionIdToken] as string;

    return new HttpResponseMessage()
    {
        Content = new StringContent("Your session ID = " + sessionId)
    };
}