【図解】セッションとトークンの違いとは?CookieとBearerトークンでログイン状態を保つしくみを初心者向けに解説

API の記事を書いていると、「ログインした状態をどうやって保っているのか」という話に何度もぶつかります。ブラウザで使う Web サイトは Cookie、API は Authorization ヘッダのトークン、と何となく使い分けられていますが、何が違うのかを自分の言葉で説明しようとすると意外と詰まりました。この記事では、セッションとトークンのしくみと違いを、RFC と公式ドキュメントで確かめながら図にしました。

この記事でわかること
  1. HTTP は前のリクエストを覚えていないので、「誰か」を毎回示すしくみが要ること
  2. セッション:サーバが状態を覚え、Cookie でセッション ID を渡すしくみ
  3. トークン:Authorization ヘッダで「証明」を毎回見せるしくみと、参照型・自己完結型の違い
  4. 状態をどこが持つか(セッション・参照型・自己完結型)と、ログアウトや停止のときの違い
  5. 企業での使われ方の例(Microsoft 365 の Cookie、Azure のマネージド ID、Meraki の API)
  6. 盗まれないための守り方(HTTPS、Cookie の属性、期限)

HTTP は前のリクエストを覚えていない

HTTP の仕様(RFC 9110)は、HTTP を「ステートレス」なプロトコルと定義しています。1つ1つのリクエストは、それだけで意味がわかるように作られていて、サーバは前のリクエストとのつながりを前提にしません。

ブラウザWeb サーバ① ログイン(ID とパスワード)ログインできました② マイページを見せてどなたですか?(①のことは覚えていない)リクエストごとに「誰か」を示すものを付けて送る必要がある
図1 サーバは前のリクエストを覚えていない

そのため、ログインのあとも「このリクエストは誰からか」を毎回示す必要があります。毎回 ID とパスワードを送るのは危ないので、ログインのときに一度だけ確かめ、その結果を表す「目印」を受け取って、以後のリクエストに付けるやり方がとられます。この目印の持ち方が、大きく分けてセッションとトークンの2つです。

しくみ状態を覚えている場所リクエストに付けるもの
セッションサーバセッション ID(Cookie に入れて送る)
トークントークンの中、またはトークンの発行元トークン(主に Authorization ヘッダで送る)

セッション:サーバが覚えて、Cookie で番号を渡す

セッションでは、ログインに成功するとサーバが「セッション情報」(誰がログインしているか、権限、期限など)を作って自分の側に保存します。ブラウザに渡すのは、その情報を引くための番号であるセッション ID だけです。

ブラウザWeb サーバセッション情報(サーバ側)SID=31d4…→ ユーザーA・権限・期限作って保存① POST /login(ID・パスワード)② Set-Cookie: SID=31d4…③ GET /mypageCookie: SID=31d4…SID で保存した情報を引く④ ユーザーA のページブラウザが持つのは番号(セッション ID)だけ。中身はサーバが覚えている
図2 セッションの流れ

番号の受け渡しには Cookie を使います。サーバは応答の Set-Cookie ヘッダで Cookie を渡し、ブラウザは同じサイトへのリクエストに Cookie ヘッダで自動的に付けて送ります。Cookie の仕様(RFC 6265)は、Cookie を「ほぼステートレスな HTTP の上で、サーバがセッションを保つ」ためのしくみと説明しています。

例:Set-Cookie と Cookie(RFC 6265 の例)

== Server -> User Agent ==

Set-Cookie: SID=31d4d96e407aad42; Path=/; Secure; HttpOnly
Set-Cookie: lang=en-US; Path=/; Domain=example.com

== User Agent -> Server ==

Cookie: SID=31d4d96e407aad42; lang=en-US

1行目の SID がセッション ID にあたります。Secure や HttpOnly は Cookie の「属性」で、Cookie をいつ・どこへ送るかを決めます。

属性意味
SecureHTTPS のような安全な通信路のときだけ送る
HttpOnlyHTTP のリクエストにだけ使い、ページのスクリプト(JavaScript)からは読めないようにする
SameSite別のサイトから始まったリクエストに Cookie を付けるかどうか。Strict(同じサイトからだけ)、Lax(別のサイトからはリンクで移動するときの GET などに限る)、None(付ける。Secure が必須)
Expires/Max-AgeCookie の有効期限。両方あれば Max-Age が優先される。どちらもなければ、ブラウザを閉じると消える「セッション Cookie」になる
Domain/Pathどのホスト・どのパスへのリクエストに付けるか
「セッション Cookie」と「セッション」

名前が似ていますが別のものです。セッション Cookie は「期限を指定していない Cookie」のことで、ログインのセッションとは関係なく使われます。逆に、ログインのセッション ID を、期限付きの Cookie に入れることもあります。

トークン:「証明」を持たせて毎回見せる

トークンは、ログイン(サインイン)の結果として発行される文字列で、リクエストのたびに見せることで「誰の、どんな権限のリクエストか」を示します。OAuth 2.0 の仕様(RFC 6749)では、保護されたリソースにアクセスするための資格情報をアクセストークンと呼びます。API では、このトークンを Authorization ヘッダに入れて送るのが一般的です。

アプリトークンの発行元API サーバ① サインイン② アクセストークン③ GET /resourceAuthorization: Bearer mF_9.B5f-4.1JqMトークンを確かめる④ データAPI はトークンを見て、誰の・どんな権限のリクエストかを判断する
図3 トークンの流れ

トークンの送り方を決めた RFC 6750 では、Bearer という種類を使って次のように送ります。

例:Bearer トークンを付けたリクエスト(RFC 6750 の例)

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

トークンの期限が切れていたり、正しくなかったりすると、API は 401 Unauthorized を返し、WWW-Authenticate ヘッダで理由を伝えます。

例:期限切れのトークンへの応答(RFC 6750 の例)

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                  error="invalid_token",
                  error_description="The access token expired"

GitHub の REST API や Microsoft Graph も、この Authorization: Bearer の形でトークンを受け付けます。

Bearer は「持っている人」

Bearer は「持参人」という意味です。RFC 6750 は、Bearer トークンを「持っている人なら誰でも、ほかの持ち主と同じように使えるトークン」と定義しています。使うときに鍵を持っていることの証明は求められません。つまり、トークンを盗まれると、そのまま本人として使われてしまいます。

参照型と自己完結型

RFC 6749 では、アクセストークンには2つの作り方があるとしています。1つは、認可の情報を引くための識別子(番号)になっているもの。もう1つは、認可の情報そのものを、確かめられる形(データと署名)で中に持っているものです。この記事では、前者を参照型、後者を自己完結型と呼びます。

参照型中身のない番号自己完結型中身+署名ランダムな文字列API発行元この番号は有効?有効・ユーザーA・権限API は使われるたびに発行元へ問い合わせて中身を知る(イントロスペクション:RFC 7662)ユーザーA権限期限署名API発行元の鍵で署名を確かめ、期限を見て、中身を読むAPI は発行元に聞かずにその場で判断できる(よく使われる形式:JWT)
図4 参照型と自己完結型のトークン
型API の確かめ方特徴
参照型発行元に「このトークンは有効か」を問い合わせる。RFC 7662 のトークンイントロスペクションがこの問い合わせの決まり発行元で取り消せばすぐに無効になる。そのかわり、毎回問い合わせが発生する
自己完結型署名と期限を確かめ、中身を読む。発行元へは問い合わせない毎回の問い合わせが要らない。そのかわり、期限前に取り消すのが難しい

自己完結型でよく使われる形式が JWT(JSON Web Token)です。署名のしくみは暗号の基礎の記事で説明しています。

状態をどこが持つか

セッション、参照型、自己完結型の違いは、「ログインの状態(誰か、権限、期限)をどこが持っているか」で整理するとわかりやすくなります。

セッション参照型トークン自己完結型トークンCookie: SIDWeb サーバ状態(誰・権限・期限)受け取ったサーバ自身が持つBearer 番号API問い合わせ発行元状態(誰・権限・期限)発行元が持ち、API が聞きに行く状態(誰・権限・期限)署名API発行元の鍵だけトークンの中にある(API は保存しない)
図5 状態の置き場所
項目セッション参照型トークン自己完結型トークン
状態を持つ場所受け取ったサーバ自身トークンの発行元トークンの中
手元に来るもの番号(セッション ID)番号(中身のない文字列)中身+署名
受け取った側の
確かめ方
自分の保存した情報を引く発行元に問い合わせる発行元の鍵で署名を確かめ、期限を見る
受け取った側が
保存するもの
セッション情報なし(毎回聞く)発行元の鍵だけ

参照型は「サーバ側が状態を持つ」点でセッションと同じですが、持っているのが使う先の API ではなく、トークンの発行元である点が違います。自己完結型は、API が状態を保存しなくても、トークンに書かれた中身で判断できます。中身を信用できるのは署名があるからで、書き換えれば署名が合わなくなり、API は受け付けません。

発行元は状態を持っている

自己完結型でも、システム全体から状態がなくなるわけではありません。期限の短いアクセストークンを取り直すためのリフレッシュトークンやサインインのセッションは、発行元の側で管理されます。Microsoft のドキュメントでも、サインインのセッションにあたるトークンは取り消せる作りで、アプリ用のアクセストークンは基本的に取り消せない、と分けて説明しています。「状態がない」のではなく、「API が状態を持たなくてよい」と考えるのが正確です。

署名は「隠す」ものではない

署名は、書き換えられていないことを確かめるためのもので、中身を隠すものではありません。JWT は署名だけのものと暗号化したものの両方を作れますが(RFC 7519)、署名だけの JWT は中身をデコードすれば読めます。Microsoft のドキュメントでも、検証やデバッグのために jwt.ms のようなサイトで JWT をデコードできると説明しています。RFC 7519 は、プライバシーにかかわる情報をトークンに入れないことが、いちばん簡単な対策だとしています。

Windows のアクセストークンとは別物

Windows にも「アクセストークン」という言葉がありますが、これは Windows がプロセスのユーザーやグループを表すために使う内部の情報で、この記事のトークンとは別のものです。Windows のほうはアクセストークンと ACL の記事で説明しています。

セッションとトークンの違い

どちらも「ログインの結果を表す目印を、リクエストのたびに見せる」点は同じです。違いは、状態をどこに置くかと、どうやって送るかです。

項目セッショントークン
状態を覚える場所サーバ(セッション情報)トークンの中(自己完結型)、または発行元(参照型)
送り方Cookie。ブラウザが自動で付ける主に Authorization ヘッダ。アプリが自分で付ける
向いている相手ブラウザで使う Web サイトAPI、スマホやPCのアプリ、サービスどうしの呼び出し
ログイン先と使う先同じサイト発行元と API が別でもよい
取り消しサーバがセッション情報を消せば、次のリクエストから使えない参照型はすぐ取り消せる。自己完結型は期限まで使える場合がある
ログアウト・アカウント停止セッション使えるサーバが消す → 次のリクエストから使えないトークン(自己完結型)使えるまだ使える場合がある使えないトークンの期限時間だからトークンは期限を短くし、更新のしくみと組み合わせる
図6 ログアウトや停止のあとの違い

Microsoft のドキュメントでも、この違いが書かれています。Microsoft Entra ID が発行するアクセストークンの既定の期限は 60〜90 分(平均 75 分)です。アクセストークンを使うアプリでは、アカウントを止めても、ユーザーがアクセスできなくなるのは手元のアクセストークンの期限が切れたときです(継続的アクセス評価に対応したアプリでは、もっと早く取り消せます)。また、ブラウザで使う多くの Web アプリは、サインインのあとアプリ自身のセッション(Cookie)を発行し、それを Entra ID から直接取り消すことはできない、と説明しています。

Cookie にトークンを入れることもある

「Cookie ならセッション、ヘッダならトークン」ときっちり分かれるわけではありません。Cookie は入れ物で、そこにトークンを入れる設計もあります。分けて考えると、「状態をサーバが持つか、トークンが持つか」と「Cookie で送るか、ヘッダで送るか」の2つの軸があります。

企業での使われ方(例)

会社の中で、Cookie と Bearer トークンがどう使われているかを例にしました。製品名と Cookie の名前、リクエストの形は、どれも公式ドキュメントで確かめたものです。

送る側送るもの受け取る側Cookie社員のブラウザESTSAUTHMicrosoft Entra IDサインインの状態(SSO)Cookie社員のブラウザFedAuth・rtFASharePointアプリ自身のセッションCookie社員のブラウザ認証 Cookie社内 Web アプリApp Service の認証機能Bearer運用スクリプトアクセストークンMicrosoft Graphユーザーやグループの操作BearerAzure の VMアクセストークンAzure Resource Managerマネージド ID で取得Bearer自動化ツールAPI キーMeraki Dashboard API管理者が発行した鍵Cookie はブラウザが自動で付け、Bearer はスクリプトやツールが自分で Authorization ヘッダに付ける
図7 企業での使われ方の例
場面送るものしくみ
社員が Microsoft 365 にサインインするCookie(ESTSAUTH・ESTSAUTHPERSISTENT)Microsoft Entra ID のサインインの状態を表す Cookie。SSO に使われる
社員が SharePoint を開くCookie(FedAuth・rtFA)SharePoint 自身のセッション。サインインの確認は Entra ID に任せ、そのあとは SharePoint の Cookie で利用者を見分ける
社内の Web アプリに Entra ID でサインインさせるCookieAzure App Service の認証機能では、サインインのあとに App Service が認証済みの Cookie を応答に付け、ブラウザが以後のリクエストに自動で付ける
運用スクリプトで Entra ID のユーザーを作るAuthorization: BearerMicrosoft Graph の POST /users などに、アクセストークンを付けて呼ぶ
Azure の VM 上のプログラムが Azure を操作するAuthorization: Bearerマネージド ID で VM 自身がトークンをもらい、Azure Resource Manager や Storage を呼ぶ。プログラムにパスワードを書かなくてよい
自動化ツールで Meraki の設定を取得・変更するAuthorization: BearerMeraki Dashboard API は、管理者が発行した API キーを Bearer の形で受け取る

Cookie の例:Microsoft 365 にサインインしたブラウザ

Microsoft 365 をブラウザで使うと、ブラウザには少なくとも2種類の Cookie が入ります。1つは Microsoft Entra ID(login.microsoftonline.com)の Cookie で、サインインの状態を表し、SSO に使われます。もう1つは、使うサービス(たとえば SharePoint)自身の Cookie です。

社員のブラウザ持っている CookieESTSAUTHFedAuth・rtFACookie: ESTSAUTH…Cookie: FedAuth…Microsoft Entra IDlogin.microsoftonline.comサインインの状態(SSO)SharePoint*.sharepoint.comSharePoint 自身のセッションSharePoint に入るとき、Entra ID でサインインを確かめるCookie はドメインごとに分かれ、それぞれの送り先にだけ送られる
図8 Microsoft 365 を使うブラウザの Cookie
Cookie送り先役割(公式ドキュメントより)
ESTSAUTHEntra IDSSO のためのセッション情報。ブラウザを閉じると消える
ESTSAUTHPERSISTENTEntra IDSSO のためのセッション情報。ブラウザを閉じても残る
FedAuthSharePointSharePoint のトップレベルのサイトごとの認証 Cookie
rtFASharePointSharePoint 全体で使う認証 Cookie。別のトップレベルのサイトに移ったとき、サインイン画面を出さずに利用者を確かめる

SharePoint の Cookie は既定ではセッション Cookie(ブラウザを閉じると消える)で、サインインのときに「サインインしたままにする」を選ぶと、ブラウザを閉じても残る Cookie になります。

ここで、前の節の「アプリのセッションは Entra ID から直接取り消せない」がつながります。Entra ID のサインインの状態と、SharePoint のようなアプリのセッションは、別々の Cookie で別々に持たれているからです。

Bearer の例:Azure の VM がマネージド ID で Azure を操作する

Azure の VM でマネージド ID を有効にすると、VM の中のプログラムは、VM の中からしか届かないエンドポイントにトークンを頼めます。プログラムにパスワードやキーを書く必要がありません。

① トークンをもらう(公式ドキュメントの例)

curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -H Metadata:true

応答(公式ドキュメントの例。access_token は省略されています)

{
  "access_token":"eyJ0eXAiOi...",
  "refresh_token":"",
  "expires_in":"3599",
  "expires_on":"1504130527",
  "not_before":"1504126627",
  "resource":"https://management.azure.com",
  "token_type":"Bearer"
}

token_type が Bearer なので、このトークンは Authorization ヘッダに Bearer の形で付けて使います。expires_in は残りの秒数で、この例では約1時間です。ドキュメントでは、Microsoft Entra ID がこのとき JWT 形式のアクセストークンを返すと説明しています。

② トークンを付けて Azure Resource Manager を呼ぶ(公式ドキュメントの例)

curl https://management.azure.com/subscriptions/<SUBSCRIPTION-ID>/resourceGroups/<RESOURCE-GROUP>?api-version=2016-09-01 -H "Authorization: Bearer <ACCESS-TOKEN>"

Bearer の例:Meraki のクラウド管理を API で操作する

ネットワーク機器のクラウド管理でも、Bearer の形はよく出てきます。Cisco Meraki の Dashboard API は、管理者が発行した API キーを Authorization: Bearer で送ります。

組織の一覧を取る(Meraki の公式ドキュメントの例)

curl https://api.meraki.com/api/v1/organizations \
  -L -H 'Authorization: Bearer {API_KEY}'
API キーとトークンの違い

送り方は同じ Bearer でも、Meraki の API キーは、サインインのたびに発行される期限の短いアクセストークンとは性質が違います。Meraki のドキュメントでは、API キーは発行した管理者と同じ権限を持ち、有効期限は「取り消すまで」とされています。漏れたときの影響が長く続くので、保管する場所に気をつけ、使わなくなったキーは取り消します。キーの発行と取り消しは、ダッシュボードの Organization ›› API & Webhooks ›› API keys and access で行います。Meraki には、OAuth 2.0 で期限 60 分のアクセストークンを使う方法も用意されています。

期限と更新

セッションもトークンも、いつまでも使えるわけではありません。OWASP のセッション管理のチートシートでは、セッションに2種類の期限を設けることを勧めています。

期限意味
アイドルタイムアウト操作がないまま、この時間がたつとセッションが切れる
絶対タイムアウト操作していても、セッションが続けられる最長の時間

トークンは、盗まれたときの被害を小さくするために期限を短くします。RFC 6750 も、Bearer トークンは短い期限(1時間以下)で発行するよう勧めています。期限が切れるたびにサインインし直すのは不便なので、OAuth 2.0 では、新しいアクセストークンをもらうためのリフレッシュトークンを一緒に発行するしくみがあります。Entra 参加端末で使われる PRT も、サインインの状態を保ってアクセストークンをもらうためのトークンです(PRT の記事)。

守り方:盗まれない、盗まれても長く使わせない

セッション ID もトークンも、持っている人が本人として扱われます。Microsoft のドキュメントでも、トークンを盗まれると、そのユーザーになりすましてアクセスされると説明しています。守り方の基本は、盗まれないようにすることと、盗まれても長く使わせないことです。

対策理由
必ず HTTPS で送る通信路で読み取られないようにする。RFC 6750 は、Bearer トークンを使うリクエストに TLS(HTTPS)を必須としている。Cookie には Secure を付ける
Cookie に HttpOnly と SameSite を付けるページのスクリプトから読めないようにし、別のサイトから始まったリクエストに付かないようにする。OWASP は、セッションの Cookie に SameSite=Strict(できれば)か Lax を明示するよう勧めている
URL に入れないURL は履歴やログに残りやすい。RFC 6750 は、Bearer トークンをページの URL(クエリ文字列など)で渡さないよう求めている。OWASP も、セッション ID は Cookie でやり取りするよう勧めている
ログイン後にセッション ID を作り直すログインの前から使われていた ID を、そのまま引き継がないため。OWASP は、ログインや権限の変更のたびにセッション ID を新しくするよう勧めている
ログアウトでサーバ側を消すCookie を消すだけでなく、サーバのセッション情報も無効にする
期限を短くする盗まれても使える時間を短くする。トークンは更新のしくみと組み合わせる
ログに残さないサーバのログにトークンを残すと、そこから漏れる。Microsoft も、サーバ側のログにトークンを記録・保存しないよう勧めている

Microsoft は、利用者とサービスの間に入り込む手口のフィッシングでは、多要素認証をしていてもセッション Cookie が奪われることがある、とも説明しています。サインインそのものを守るには、フィッシングに強い認証方式(FIDO2 セキュリティキーの記事)が有効です。

確かめ方:ブラウザの開発者ツール

Cookie の中身と属性は、ブラウザの開発者ツールで見られます。Chrome では Application ›› Storage ›› Cookies を開いてサイト(オリジン)を選ぶと、Name、Value、Domain、Path、Expires / Max-Age、HttpOnly、Secure、SameSite などの列が並びます。どのリクエストに Cookie や Authorization ヘッダが付いているかは、Network パネルでリクエストを選び、リクエストヘッダを見ると確かめられます。

見えている値はセッション ID やトークンそのものなので、画面を共有したり、スクリーンショットを人に渡したりするときは隠してください。

まとめ

項目ポイント
前提HTTP はステートレス。ログインの結果を表す目印を、リクエストのたびに付ける
セッション状態はサーバが覚える。ブラウザはセッション ID を Cookie で送る
トークンAuthorization: Bearer で送る。参照型は発行元に問い合わせ、自己完結型は署名と期限で確かめる
状態の置き場所セッションは受け取ったサーバ、参照型は発行元、自己完結型はトークンの中。自己完結型でも発行元はサインインの状態を持っている
企業の例Microsoft 365 は Entra ID の Cookie とアプリの Cookie を別々に持つ。Azure のマネージド ID や Meraki の API は Authorization: Bearer で送る
使い分けブラウザの Web サイトはセッション、API やアプリはトークンが多い
取り消しセッションはサーバが消せばすぐ止まる。自己完結型トークンは期限まで残ることがあるので、期限を短くする
守り方HTTPS、Secure・HttpOnly・SameSite、URL やログに入れない、ログイン後に ID を作り直す

参考にした資料

この記事は、次の標準と公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。

あとがき

図にしてみると、セッションとトークンの違いは「状態をどこに置くか」に尽きる、というのがいちばんの気づきでした。サーバが覚えているから止めやすいセッションと、サーバが覚えていないから身軽な代わりに止めにくいトークン。アカウントを止めたのにしばらくアクセスできてしまう、という話の理由も、この違いで説明がつきます。

コメント