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
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 を削除します。
Expires
と Max-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;
}
AddCookies は CookieHeaderValue インスタンスの配列を受け取ることに注意します。
クライアント要求から 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 の数 (合計数とドメインあたりの数の両方) が制限されます。 そのため、複数の 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 を追加したりできます。
次のコードは、セッション 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)
};
}