認証とセッション

 認証とセッション⑤

 ログインしたことを、Webサイトはどう覚えているのか

前回は、ICカードを読み取り、C#で社員を探して打刻する仕組みを作りました。カードIDからHMAC照合値を作り、その計算に使う秘密鍵を保管するところまで扱いました。

今回は、Webサイトへログインした「後」の話です。

一度ログインすると、一覧から詳細へ移動しても、毎回パスワードを求められることはありません。カードでログインするサービスでも、認証を終えれば、カードをリーダーから外して利用を続けられることがあります。

一方で、入力に時間をかけていたら「もう一度ログインしてください」と言われることもあります。買い物かごの商品は残っているのに、ログインだけ切れていることもあります。

Webサイトは何を覚え、何を確かめているのでしょうか。Cookie、セッションID、認証チケット、JWTを、順に整理してみます。

最初の本人確認と、その後の確認を分ける

ログイン成功後、サーバーが発行した認証情報をブラウザーがCookieに保存し、次回送り返す流れ
発行するのはサーバー。ブラウザーは保存し、条件に合うアクセスで送り返します。

勤怠サイトを例にします。最初にユーザーIDとパスワードを入力してログインすると、自分の勤務実績が表示されます。次に「先月」を選ぶと、先月の勤務実績が表示されます。

このとき、ブラウザーはサーバーへ新しいリクエストを送っています。HTTPのやり取りだけで、「先ほどログインしたユーザーAの続きだ」と自動的に分かるわけではありません。

そこで、ログイン成功時に、その後のリクエストでも提示できる情報を渡しておきます。

最初はパスワードやカードで本人を確かめ、その後は、発行した認証情報を確認してログイン状態を引き継ぎます。

毎回パスワードを送り直しているわけでも、接続が続いているから本人だと判断しているわけでもありません。サーバーは、リクエストに添えられた情報を確認し、その都度「誰として処理するか」を判断します。

このように、複数のやり取りを一続きのものとして扱う仕組みを、セッションと呼びます。ここでは、その中でもログイン状態を維持する仕組みに注目します。

Cookieは、ブラウザーが次のアクセスに添えるデータ

Cookieは、ブラウザーが保存し、条件に合うアクセスに添えて送る小さなデータです。

ログインに成功すると、サーバーは応答のSet-Cookieヘッダーで、認証に使うCookieをブラウザーへ渡します。ブラウザーは保存したCookieを、以後、送信先や有効期限などの条件に合うリクエストのCookieヘッダーへ載せます。[1]

勤怠サイトで「先月」をクリックすれば、「先月の勤務実績を表示して」という要求にCookieが添えられます。サーバーはCookieの中身を確認し、ユーザーAとして認証できれば、Aに許可された勤務実績を返します。

Cookieの送信はブラウザーが行うので、利用者が毎回何かを選び直す必要はありません。

ただし、Cookieは情報を保存して送る仕組みの名前です。何が入っているかは、アプリによって違います。 次に、その中身の代表的な構成を見てみましょう。

Cookieは認証専用ではなく、表示言語などの設定にも使われます。Cookieが存在するだけでログイン済みになるわけではありません。サーバーが、その中身を有効な認証情報として受け付ける必要があります。

セッションIDで、サーバー側の記録を探す

セッションIDはサーバー側の記録を探し、認証チケットは保護を検証してユーザー情報を取り出す比較
記録を探すためのIDか、保護されたユーザー情報を含むチケットかが違いです。

一つ目は、サーバー側にログイン状態の記録を置き、その記録を探すためのIDをブラウザーへ渡す方法です。

ログインに成功すると、サーバーは推測しにくいランダムなセッションIDを発行します。そして、そのIDにユーザーや有効期限などを対応付けて保存します。[2]

たとえるなら、ブラウザーが持つのは受付番号で、サーバーが受付台帳を持つ形です。

保存先保存する内容の例
ブラウザーのCookieランダムなセッションID
サーバー側の記録そのIDに対応するユーザーA、有効期限など

次のリクエストでIDが届いたら、サーバーは対応する記録を探します。記録が見つかり、まだ有効なら、そのユーザーとして扱います。

記録の保存先にはメモリ、データベース、キャッシュなどが使われます。ブラウザーから送られてきた「私はユーザーAです」という文字だけを信用する構成ではありません。

認証チケットで、保護された情報を確かめる

もう一つは、サーバーがユーザーに関する情報などをまとめ、保護した認証チケットを発行してブラウザーへ渡す方法です。セッションIDも認証チケットも、発行するのはサーバーです。ブラウザーは受け取ったものをCookieとして保存し、次のアクセスで送り返します。

次回使うASP.NET Coreの標準的なCookie認証では、認証チケットをデータに変換し、暗号化と改ざん検知の保護を施してCookieへ格納します。[3]

チケットには、ユーザーを識別する情報や、発行日時・有効期限などの認証に関する情報を含められます。このうち「ユーザーIDは何か」「どの役割に属するか」といった情報を、クレイム(Claim)と呼びます。

次のアクセスでは、サーバーが保護されたチケットを受け取り、保護を検証して中身を取り出します。期限などの条件も確認し、そのリクエストのユーザー情報を復元します。

セッションIDが「この受付番号に対応する記録は何か」を調べるのに対して、こちらは「この証票の内容を受け付けてよいか」を確かめるイメージです。

構成次のアクセスで主に確認するもの
セッションIDで参照するサーバー側にある、IDに対応する記録
保護された認証チケットを渡すチケットの保護、有効期限、含まれている情報

チケットを使っていても、アカウントの無効化や権限変更を反映するため、サーバー側の情報を追加で確認する場合があります。「チケットならデータベースを一切見ない」とは限りません。

また、チケット本体をサーバー側に保存し、Cookieには参照用の識別子を入れる設定もあります。認証チケットという言葉だけで、保存場所まで一意に決まるわけではありません。[4]

JWTも、クレイムを受け渡す形式の一つ

透明スリーブの署名付きJWTと保護された認証チケット、およびCookieとAuthorizationヘッダーの組み合わせ
データの形式と送信方法は別です。JWTをCookieに入れて送る実装もあります。

JWT(JSON Web Token)は、クレイムをJSONで表して受け渡すための形式です。Web APIへ提示するアクセストークンなどに使われます。

よく使われる署名付きJWTは、ヘッダー、ペイロード、署名部分で構成されます。クレイムはペイロードに入ります。署名やHMACで、ヘッダーとペイロードの改ざんを検知できるようにします。[5]

④で登場したHMAC-SHA-256も、JWTのHS256というアルゴリズムで使われます。カードIDの照合に使ったときと計算対象は違いますが、秘密鍵がなければ正しい検証値を作れない、という考え方は共通しています。

RSAやECDSAによる署名を使う場合は、発行側が秘密鍵を持ち、検証側が公開鍵を使います。JWTが必ずHMACで保護されるわけではありません。[9]

ここで大切なのは、署名付きJWTの中身は、通常、読めるということです。 Base64urlという表現に変換されていますが、これは暗号化ではありません。

読めることと、書き換えて正しいJWTとして受け付けてもらえることは別です。中のユーザーIDを変更しても、署名やHMACの検証に通らなければ受け付けてもらえません。利用側は、署名だけでなく期限・発行元・宛先なども適切に確認します。

比較するもの署名付きJWTASP.NET Coreの標準的なCookie認証チケット
主な中身ユーザーなどに関するクレイムクレイムと認証に関する属性
保護署名またはHMACによる改ざん検知暗号化と改ざん検知
中身を読むには通常、秘密鍵は不要保護を解除できる鍵が必要

JWTには暗号化する形式もあります。ここでは、よく使われる署名付きJWTと比較しています。また、ASP.NET Coreの標準的なCookie認証チケットは、JWT形式ではありません。

Cookie・トークン・Identityを、同じ分類に並べない

「Cookie認証とJWT認証はどう違う?」という説明を見かけます。ただ、Cookieは主に保存・送信の仕組み、JWTはデータ形式を指しています。

JWTをCookieに入れて送る実装もあります。一方、アプリがAPIを呼ぶときに、Authorization: Bearer …というヘッダーを付けて送る実装もあります。

Bearerは「持参人」という意味で、トークンを持っている人が、それを提示してアクセスする方式です。サーバーはトークンの有効性や許可範囲を検証してから受け付けます。トークンを持つことに加えて、そのトークン専用の秘密鍵を持っている証明を毎回求める方式ではありません。また、Bearerトークンの中身がJWTとは限らず、ランダムな識別子である場合もあります。[6]

「トークン」という言葉自体も広く、セッションIDのような参照用の値を指す場合があります。「セッション・チケット・トークン」という三つの排他的な方式がある、と考えると混乱します。

何について比べているかを分けると、整理できます。

観点例
最初にどう本人を確かめるかパスワード、パスキー、電子証明書
ログイン後、何を確認するかIDに対応する記録、保護されたクレイムなど
どんな形式で保護するか署名付きJWT、保護された認証チケットなど
どう送るかCookie、Authorizationヘッダーなど
どの仕組みで実装するかASP.NET Core Identity、Cookie認証ミドルウェアなど

次回使うASP.NET Core Identityは、ユーザーやパスワード、ロール、サインインなどを扱う仕組みです。Identityという名前が、セッションIDやJWTのようなデータ形式を意味するわけではありません。[7]

.NET Frameworkで使われてきたOWINのCookie認証でも、保護された認証チケットをCookieで受け渡す構成を取れます。OWINは処理を組み合わせるための仕様であり、その上で動くCookie認証ミドルウェアが発行・検証を担当します。[8]

ASP.NET CoreのISessionなどで一時的なアプリのデータを保存することと、Cookie認証でログイン状態を維持することは、分けて考えます。Cookie認証のために、必ずISessionへユーザーIDを保存する必要はありません。

買い物かごは残るのに、ログインは切れる?

ログイン期限切れ後に再ログインし、ユーザーIDに対応する買い物かごをDBから読み出す例
ログインの有効期限と、買い物かごの保存期間は別に管理できます。

ここで、冒頭の身近な例へ戻ります。

たとえば、ログインの有効期限が切れたあと、再ログインすると、前に入れた商品が買い物かごに再び表示されるケースがあります。

  1. ログインの有効期限が切れます。
  2. 再ログインして、サーバーがユーザーAであることを確認します。
  3. ユーザーAのIDにひも付いた買い物かごを、データベースから読み込みます。
  4. 保存してあった商品が、画面に再び表示されます。

ログイン状態は切れても、データベースに保存した買い物かごは消えていません。 「復元」といっても、消えたデータを戻すのではなく、保存してあったデータを再び読み出しているわけです。

これは実装の一例です。未ログインでも、別のCookieの識別子を手掛かりに買い物かごを表示し、購入手続きで改めてログインを求める構成もあります。

大切なのは、買い物かごの保存期間と、ログイン状態の有効期限は別に管理できるという点です。「商品が残っているから、ログインも有効」とは限りません。

入力フォームでも同様です。入力途中の文字はブラウザー上に残っていても、送信時点で認証情報が期限切れなら、サーバーはそのまま処理を受け付けられないことがあります。

ただし、「セッションが切れました」という表示が、常に認証Cookieの期限切れを意味するとは限りません。入力途中のデータや、操作確認用の情報に期限を設けているアプリもあります。画面の文言だけで、どの情報が失効したかは決まりません。

Cookieを消すことと、受け付けなくすることは別

ブラウザーがCookieを保存する期間と、サーバーが認証情報を有効と判断する期間も、別のものです。

Cookieが送られてきても、対応する記録が失効していたり、チケットの期限が切れていたりすれば、ログイン済みとしては扱いません。利用を続けると期限を延長する設定もあります。[1][3]

ログアウト時には、ブラウザーの認証Cookieを削除する処理を行います。セッションIDで記録を参照する構成なら、サーバー側の対応する記録も無効にできます。

一方、ブラウザーからCookieを削除するだけでは、別の場所にコピーされたチケットまで消えるわけではありません。コピーも受け付けなくするには、失効を確認する仕組みなどが必要になります。[2][3]

また、ブラウザーを閉じることは、明示的なログアウトと同じではありません。保存期間の設定やブラウザーの復元機能によって、Cookieが残る場合もあります。[1]

ログイン後の情報も、大切に扱う

サーバーがセッションIDやチケットを本人に代わる証拠として受け付けるなら、それらを他人に渡さないことが大切です。

暗号化されていて中身を読めなくても、そのまま送り返して利用できる場合があります。 「チケットは暗号化されているから、漏れても大丈夫」とは言えません。

Cookieには、HTTPSと併せて次のような属性を使います。[1]

属性主な役割
SecureHTTPS通信で送信するよう制限する
HttpOnlyJavaScriptからCookieの値を直接読み取れなくする
SameSite別サイトを起点とするアクセスでのCookie送信を制御する

HttpOnlyを付けても、ブラウザーが条件に合うリクエストへCookieを添える動作は続きます。この自動送信という性質は、⑦で扱うCSRFを理解するときにも関係します。

次回

次回は、「認証とセッション⑥ ― C#とIdentityで、ログインからログアウトまで作る」です。

今回の認証チケットとCookieの仕組みを、ASP.NET Core Identityの実装で確認します。ログイン画面への誘導、入力の照合、Cookieの発行、ログイン済みのページ表示、ログアウトまでを、一つのアプリとしてつなげます。

参考資料

  1. MDN:Using HTTP cookies
  2. OWASP:Session Management Cheat Sheet
  3. Microsoft Learn:Use cookie authentication without ASP.NET Core Identity
  4. Microsoft Learn:CookieAuthenticationOptions.SessionStore
  5. RFC 7519:JSON Web Token
  6. RFC 6750:OAuth 2.0 Bearer Token Usage
  7. Microsoft Learn:Introduction to Identity on ASP.NET Core
  8. Microsoft:Katana CookieAuthenticationMiddleware
  9. RFC 7518:JSON Web Algorithms

返信を残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA