ASP.NET Web API の HTTP Cookie

このトピックでは、Web API で HTTP Cookie を送受信する方法について説明します。

HTTP Cookie の背景

このセクションでは、Cookie が HTTP レベルでどのように実装されるかについて簡単に説明します。 詳細については、RFC 6265 を参照してください。

Cookie は、サーバーが HTTP 応答で送信するデータの一部です。 クライアントは (必要に応じて) Cookie を格納し、後続の要求でそれを返します。 これにより、クライアントとサーバーは状態を共有できます。 Cookie を設定するために、サーバーは応答に Set-Cookie ヘッダーを含めます。 Cookie の形式は、省略可能な属性を持つ名前と値のペアです。 次に例を示します。

Set-Cookie: session-id=1234567

属性の例を次に示します。

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

Cookie をサーバーに返すために、クライアントは後の要求に Cookie ヘッダーを含めます。

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.

HTTP 応答には、複数の Set-Cookie ヘッダーを含めることができます。

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

クライアントは、1 つの Cookie ヘッダーを使用して複数の Cookie を返します。

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

Cookie のスコープと期間は、Set-Cookie ヘッダー内の次の属性によって制御されます。

  • ドメイン: Cookie を受け取るドメインをクライアントに伝えます。 たとえば、ドメインが "example.com" の場合、クライアントは cookie を example.com のすべてのサブドメインに返します。 指定しない場合、ドメインは配信元サーバーです。
  • パス: ドメイン内の指定されたパスに Cookie を制限します。 指定しない場合は、要求 URI のパスが使用されます。
  • 有効期限: Cookie の有効期限を設定します。 クライアントは、有効期限が切れると Cookie を削除します。
  • 最長有効期間: Cookie の最長有効期間を設定します。 最長有効期間に達すると、クライアントは Cookie を削除します。

ExpiresMax-Age の両方が設定されている場合は、Max-Age が優先されます。 どちらも設定されていない場合、クライアントは現在のセッションが終了したときに Cookie を削除します。 ("セッション" の正確な意味は、ユーザー エージェントによって決まります)。

ただし、クライアントは Cookie を無視する可能性があることに注意してください。 たとえば、プライバシー上の理由から、ユーザーが Cookie を無効にすることがあります。 クライアントは、有効期限が切れる前に Cookie を削除したり、保存される Cookie の数を制限したりする可能性があります。 プライバシー上の理由から、クライアントは多くの場合、ドメインが配信元サーバーと一致しない "サード パーティ" Cookie を拒否します。 つまり、サーバーが設定した Cookie を取得することに、サーバーが依存しないようにする必要があります。

Web API の Cookie

HTTP 応答に Cookie を追加するには、Cookie を表す CookieHeaderValue インスタンスを作成します。 次に、AddCookies 拡張メソッドを呼び出します。これが定義されているのは、System.Net.Http.HttpResponseHeadersExtensions クラスです。Cookie を追加するためにこれを行います。

たとえば、次のコードは、コントローラー アクション内に Cookie を追加します。

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

AddCookiesCookieHeaderValue インスタンスの配列を受け取ることに注意します。

クライアント要求から Cookie を抽出するには、GetCookies メソッドを呼び出します。

string sessionId = "";

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

CookieHeaderValue には、CookieState インスタンスのコレクションが含まれています。 各 CookieState は、1 つの Cookie を表します。 次に示すように、インデクサー メソッドを使用して CookieState を名前で取得します。

多くのブラウザーでは、保存する Cookie の数 (合計数とドメインあたりの数の両方) が制限されます。 そのため、複数の Cookie を設定する代わりに、構造化データを 1 つの Cookie に格納すると便利な場合があります。

Note

RFC 6265 では、Cookie データの構造は定義されていません。

CookieHeaderValue クラスを使用すると、Cookie データの名前と値のペアの一覧を渡すことができます。 これらの名前と値のペアは、Set-Cookie ヘッダーで URL エンコードされたフォーム データとしてエンコードされます。

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

前のコードでは、次の Set-Cookie ヘッダーが生成されます。

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

CookieState クラスは、要求メッセージ内の Cookie からサブ値を読み取るインデクサー メソッドを提供します。

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

例: メッセージ ハンドラーで Cookie を設定および取得する

前の例では、Web API コントローラー内から Cookie を使用する方法を示しました。 もう 1 つのオプションは、メッセージ ハンドラーを使用することです。 メッセージ ハンドラーは、コントローラーよりも前のパイプラインで呼び出されます。 メッセージ ハンドラーは、要求がコントローラーに到達する前に要求から Cookie を読み取ったり、コントローラーが応答を生成した後に応答に Cookie を追加したりできます。

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

次のコードは、セッション ID を作成するためのメッセージ ハンドラーを示しています。 セッション ID は Cookie に格納されます。 ハンドラーは、セッション Cookie の要求を確認します。 要求に Cookie が含まれていない場合、ハンドラーは新しいセッション ID を生成します。 どちらの場合も、ハンドラーはセッション ID を HttpRequestMessage.Properties プロパティ バッグに格納します。 また、セッション Cookie を HTTP 応答に追加します。

この実装では、クライアントからのセッション ID が実際にサーバーによって発行されたことは検証されません。 これを認証の形式として使用しないでください。 この例のポイントは、HTTP Cookie の管理を示す点です。

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

コントローラーは、HttpRequestMessage.Properties プロパティ バッグからセッション ID を取得できます。

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

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