Cookies HTTP dans l’API web ASP.NET

Cette rubrique explique comment envoyer et recevoir des cookies HTTP dans l’API web.

Arrière-plan sur les cookies HTTP

Cette section donne une brève vue d’ensemble de la façon dont les cookies sont implémentés au niveau HTTP. Pour plus d’informations, consultez RFC 6265.

Un cookie est un élément de données qu’un serveur envoie dans la réponse HTTP. Le client (éventuellement) stocke le cookie et le retourne sur les demandes suivantes. Cela permet au client et au serveur de partager l’état. Pour définir un cookie, le serveur inclut un en-tête Set-Cookie dans la réponse. Le format d’un cookie est une paire nom-valeur, avec des attributs facultatifs. Par exemple :

Set-Cookie: session-id=1234567

Voici un exemple avec des attributs :

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

Pour renvoyer un cookie au serveur, le client inclut un en-tête Cookie dans les demandes ultérieures.

Cookie: session-id=1234567

Diagramme du processus permettant de retourner un cookie au serveur, au cours duquel le client inclut un en-tête Cookie dans les demandes ultérieures.

Une réponse HTTP peut inclure plusieurs en-têtes Set-Cookie.

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

Le client retourne plusieurs cookies à l’aide d’un en-tête cookie unique.

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

L’étendue et la durée d’un cookie sont contrôlées par les attributs suivants dans l’en-tête Set-Cookie :

  • Domaine : indique au client quel domaine doit recevoir le cookie. Par exemple, si le domaine est « example.com », le client retourne le cookie à chaque sous-domaine de example.com. S’il n’est pas spécifié, le domaine est le serveur d’origine.
  • Chemin d’accès : limite le cookie au chemin spécifié dans le domaine. S’il n’est pas spécifié, le chemin d’accès de l’URI de requête est utilisé.
  • Expire : définit une date d’expiration pour le cookie. Le client supprime le cookie à son expiration.
  • Max-Age : définit l’âge maximal du cookie. Le client supprime le cookie lorsqu’il atteint l’âge maximal.

Si et ExpiresMax-Age sont définis, Max-Age est prioritaire. Si aucun des deux n’est défini, le client supprime le cookie à la fin de la session active. (La signification exacte de « session » est déterminée par l’agent utilisateur.)

Toutefois, n’oubliez pas que les clients peuvent ignorer les cookies. Par exemple, un utilisateur peut désactiver les cookies pour des raisons de confidentialité. Les clients peuvent supprimer les cookies avant leur expiration, ou limiter le nombre de cookies stockés. Pour des raisons de confidentialité, les clients rejettent souvent les cookies « tiers », où le domaine ne correspond pas au serveur d’origine. En bref, le serveur ne doit pas compter sur la récupération des cookies qu’il définit.

Cookies dans l’API web

Pour ajouter un cookie à une réponse HTTP, créez une instance CookieHeaderValue qui représente le cookie. Appelez ensuite la méthode d’extension AddCookies , qui est définie dans System.Net.Http. Classe HttpResponseHeadersExtensions , pour ajouter le cookie.

Par exemple, le code suivant ajoute un cookie dans une action de contrôleur :

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;
}

Notez que AddCookies prend un tableau d’instances CookieHeaderValue .

Pour extraire les cookies d’une requête cliente, appelez la méthode GetCookies :

string sessionId = "";

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

Un CookieHeaderValue contient une collection d’instances CookieState . Chaque CookieState représente un cookie. Utilisez la méthode de l’indexeur pour obtenir un CookieState par nom, comme indiqué.

De nombreux navigateurs limitent le nombre de cookies qu’ils vont stocker, à la fois le nombre total et le nombre par domaine. Par conséquent, il peut être utile de placer des données structurées dans un seul cookie, au lieu de définir plusieurs cookies.

Notes

RFC 6265 ne définit pas la structure des données de cookie.

À l’aide de la classe CookieHeaderValue , vous pouvez passer une liste de paires nom-valeur pour les données de cookie. Ces paires nom-valeur sont encodées sous forme de données de formulaire encodées par URL dans l’en-tête 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 });

Le code précédent génère l’en-tête Set-Cookie suivant :

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

La classe CookieState fournit une méthode d’indexeur pour lire les sous-valeurs d’un cookie dans le message de demande :

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"];
}

Exemple : Définir et récupérer des cookies dans un gestionnaire de messages

Les exemples précédents ont montré comment utiliser des cookies à partir d’un contrôleur d’API web. Une autre option consiste à utiliser des gestionnaires de messages. Les gestionnaires de messages sont appelés plus tôt dans le pipeline que les contrôleurs. Un gestionnaire de messages peut lire les cookies de la demande avant que la requête n’atteigne le contrôleur, ou ajouter des cookies à la réponse après que le contrôleur a généré la réponse.

Diagramme du processus de définition et de réception des cookies dans un gestionnaire de messages. Illustre la façon dont les gestionnaires de messages sont appelés plus tôt dans le pipeline que les contrôleurs.

Le code suivant montre un gestionnaire de messages pour la création d’ID de session. L’ID de session est stocké dans un cookie. Le gestionnaire vérifie la demande pour le cookie de session. Si la requête n’inclut pas le cookie, le gestionnaire génère un nouvel ID de session. Dans les deux cas, le gestionnaire stocke l’ID de session dans le conteneur de propriétés HttpRequestMessage.Properties . Il ajoute également le cookie de session à la réponse HTTP.

Cette implémentation ne valide pas que l’ID de session du client a été émis par le serveur. Ne l’utilisez pas comme une forme d’authentification ! Le but de l’exemple est d’afficher la gestion des 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 contrôleur peut obtenir l’ID de session à partir du conteneur de propriétés HttpRequestMessage.Properties .

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

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