API の記事を書いていると、「ログインした状態をどうやって保っているのか」という話に何度もぶつかります。ブラウザで使う Web サイトは Cookie、API は Authorization ヘッダのトークン、と何となく使い分けられていますが、何が違うのかを自分の言葉で説明しようとすると意外と詰まりました。この記事では、セッションとトークンのしくみと違いを、RFC と公式ドキュメントで確かめながら図にしました。
- HTTP は前のリクエストを覚えていないので、「誰か」を毎回示すしくみが要ること
- セッション:サーバが状態を覚え、Cookie でセッション ID を渡すしくみ
- トークン:Authorization ヘッダで「証明」を毎回見せるしくみと、参照型・自己完結型の違い
- 状態をどこが持つか(セッション・参照型・自己完結型)と、ログアウトや停止のときの違い
- 企業での使われ方の例(Microsoft 365 の Cookie、Azure のマネージド ID、Meraki の API)
- 盗まれないための守り方(HTTPS、Cookie の属性、期限)
HTTP は前のリクエストを覚えていない
HTTP の仕様(RFC 9110)は、HTTP を「ステートレス」なプロトコルと定義しています。1つ1つのリクエストは、それだけで意味がわかるように作られていて、サーバは前のリクエストとのつながりを前提にしません。
そのため、ログインのあとも「このリクエストは誰からか」を毎回示す必要があります。毎回 ID とパスワードを送るのは危ないので、ログインのときに一度だけ確かめ、その結果を表す「目印」を受け取って、以後のリクエストに付けるやり方がとられます。この目印の持ち方が、大きく分けてセッションとトークンの2つです。
| しくみ | 状態を覚えている場所 | リクエストに付けるもの |
|---|---|---|
| セッション | サーバ | セッション ID(Cookie に入れて送る) |
| トークン | トークンの中、またはトークンの発行元 | トークン(主に Authorization ヘッダで送る) |
セッション:サーバが覚えて、Cookie で番号を渡す
セッションでは、ログインに成功するとサーバが「セッション情報」(誰がログインしているか、権限、期限など)を作って自分の側に保存します。ブラウザに渡すのは、その情報を引くための番号であるセッション ID だけです。
番号の受け渡しには 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 をいつ・どこへ送るかを決めます。
| 属性 | 意味 |
|---|---|
| Secure | HTTPS のような安全な通信路のときだけ送る |
| HttpOnly | HTTP のリクエストにだけ使い、ページのスクリプト(JavaScript)からは読めないようにする |
| SameSite | 別のサイトから始まったリクエストに Cookie を付けるかどうか。Strict(同じサイトからだけ)、Lax(別のサイトからはリンクで移動するときの GET などに限る)、None(付ける。Secure が必須) |
| Expires/Max-Age | Cookie の有効期限。両方あれば Max-Age が優先される。どちらもなければ、ブラウザを閉じると消える「セッション Cookie」になる |
| Domain/Path | どのホスト・どのパスへのリクエストに付けるか |
名前が似ていますが別のものです。セッション Cookie は「期限を指定していない Cookie」のことで、ログインのセッションとは関係なく使われます。逆に、ログインのセッション ID を、期限付きの Cookie に入れることもあります。
トークン:「証明」を持たせて毎回見せる
トークンは、ログイン(サインイン)の結果として発行される文字列で、リクエストのたびに見せることで「誰の、どんな権限のリクエストか」を示します。OAuth 2.0 の仕様(RFC 6749)では、保護されたリソースにアクセスするための資格情報をアクセストークンと呼びます。API では、このトークンを Authorization ヘッダに入れて送るのが一般的です。
トークンの送り方を決めた 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 は「持参人」という意味です。RFC 6750 は、Bearer トークンを「持っている人なら誰でも、ほかの持ち主と同じように使えるトークン」と定義しています。使うときに鍵を持っていることの証明は求められません。つまり、トークンを盗まれると、そのまま本人として使われてしまいます。
参照型と自己完結型
RFC 6749 では、アクセストークンには2つの作り方があるとしています。1つは、認可の情報を引くための識別子(番号)になっているもの。もう1つは、認可の情報そのものを、確かめられる形(データと署名)で中に持っているものです。この記事では、前者を参照型、後者を自己完結型と呼びます。
| 型 | API の確かめ方 | 特徴 |
|---|---|---|
| 参照型 | 発行元に「このトークンは有効か」を問い合わせる。RFC 7662 のトークンイントロスペクションがこの問い合わせの決まり | 発行元で取り消せばすぐに無効になる。そのかわり、毎回問い合わせが発生する |
| 自己完結型 | 署名と期限を確かめ、中身を読む。発行元へは問い合わせない | 毎回の問い合わせが要らない。そのかわり、期限前に取り消すのが難しい |
自己完結型でよく使われる形式が JWT(JSON Web Token)です。署名のしくみは暗号の基礎の記事で説明しています。
状態をどこが持つか
セッション、参照型、自己完結型の違いは、「ログインの状態(誰か、権限、期限)をどこが持っているか」で整理するとわかりやすくなります。
| 項目 | セッション | 参照型トークン | 自己完結型トークン |
|---|---|---|---|
| 状態を持つ場所 | 受け取ったサーバ自身 | トークンの発行元 | トークンの中 |
| 手元に来るもの | 番号(セッション ID) | 番号(中身のない文字列) | 中身+署名 |
| 受け取った側の 確かめ方 | 自分の保存した情報を引く | 発行元に問い合わせる | 発行元の鍵で署名を確かめ、期限を見る |
| 受け取った側が 保存するもの | セッション情報 | なし(毎回聞く) | 発行元の鍵だけ |
参照型は「サーバ側が状態を持つ」点でセッションと同じですが、持っているのが使う先の API ではなく、トークンの発行元である点が違います。自己完結型は、API が状態を保存しなくても、トークンに書かれた中身で判断できます。中身を信用できるのは署名があるからで、書き換えれば署名が合わなくなり、API は受け付けません。
自己完結型でも、システム全体から状態がなくなるわけではありません。期限の短いアクセストークンを取り直すためのリフレッシュトークンやサインインのセッションは、発行元の側で管理されます。Microsoft のドキュメントでも、サインインのセッションにあたるトークンは取り消せる作りで、アプリ用のアクセストークンは基本的に取り消せない、と分けて説明しています。「状態がない」のではなく、「API が状態を持たなくてよい」と考えるのが正確です。
署名は、書き換えられていないことを確かめるためのもので、中身を隠すものではありません。JWT は署名だけのものと暗号化したものの両方を作れますが(RFC 7519)、署名だけの JWT は中身をデコードすれば読めます。Microsoft のドキュメントでも、検証やデバッグのために jwt.ms のようなサイトで JWT をデコードできると説明しています。RFC 7519 は、プライバシーにかかわる情報をトークンに入れないことが、いちばん簡単な対策だとしています。
Windows にも「アクセストークン」という言葉がありますが、これは Windows がプロセスのユーザーやグループを表すために使う内部の情報で、この記事のトークンとは別のものです。Windows のほうはアクセストークンと ACL の記事で説明しています。
セッションとトークンの違い
どちらも「ログインの結果を表す目印を、リクエストのたびに見せる」点は同じです。違いは、状態をどこに置くかと、どうやって送るかです。
| 項目 | セッション | トークン |
|---|---|---|
| 状態を覚える場所 | サーバ(セッション情報) | トークンの中(自己完結型)、または発行元(参照型) |
| 送り方 | Cookie。ブラウザが自動で付ける | 主に Authorization ヘッダ。アプリが自分で付ける |
| 向いている相手 | ブラウザで使う Web サイト | API、スマホやPCのアプリ、サービスどうしの呼び出し |
| ログイン先と使う先 | 同じサイト | 発行元と API が別でもよい |
| 取り消し | サーバがセッション情報を消せば、次のリクエストから使えない | 参照型はすぐ取り消せる。自己完結型は期限まで使える場合がある |
Microsoft のドキュメントでも、この違いが書かれています。Microsoft Entra ID が発行するアクセストークンの既定の期限は 60〜90 分(平均 75 分)です。アクセストークンを使うアプリでは、アカウントを止めても、ユーザーがアクセスできなくなるのは手元のアクセストークンの期限が切れたときです(継続的アクセス評価に対応したアプリでは、もっと早く取り消せます)。また、ブラウザで使う多くの Web アプリは、サインインのあとアプリ自身のセッション(Cookie)を発行し、それを Entra ID から直接取り消すことはできない、と説明しています。
「Cookie ならセッション、ヘッダならトークン」ときっちり分かれるわけではありません。Cookie は入れ物で、そこにトークンを入れる設計もあります。分けて考えると、「状態をサーバが持つか、トークンが持つか」と「Cookie で送るか、ヘッダで送るか」の2つの軸があります。
企業での使われ方(例)
会社の中で、Cookie と Bearer トークンがどう使われているかを例にしました。製品名と Cookie の名前、リクエストの形は、どれも公式ドキュメントで確かめたものです。
| 場面 | 送るもの | しくみ |
|---|---|---|
| 社員が Microsoft 365 にサインインする | Cookie(ESTSAUTH・ESTSAUTHPERSISTENT) | Microsoft Entra ID のサインインの状態を表す Cookie。SSO に使われる |
| 社員が SharePoint を開く | Cookie(FedAuth・rtFA) | SharePoint 自身のセッション。サインインの確認は Entra ID に任せ、そのあとは SharePoint の Cookie で利用者を見分ける |
| 社内の Web アプリに Entra ID でサインインさせる | Cookie | Azure App Service の認証機能では、サインインのあとに App Service が認証済みの Cookie を応答に付け、ブラウザが以後のリクエストに自動で付ける |
| 運用スクリプトで Entra ID のユーザーを作る | Authorization: Bearer | Microsoft Graph の POST /users などに、アクセストークンを付けて呼ぶ |
| Azure の VM 上のプログラムが Azure を操作する | Authorization: Bearer | マネージド ID で VM 自身がトークンをもらい、Azure Resource Manager や Storage を呼ぶ。プログラムにパスワードを書かなくてよい |
| 自動化ツールで Meraki の設定を取得・変更する | Authorization: Bearer | Meraki Dashboard API は、管理者が発行した API キーを Bearer の形で受け取る |
Cookie の例:Microsoft 365 にサインインしたブラウザ
Microsoft 365 をブラウザで使うと、ブラウザには少なくとも2種類の Cookie が入ります。1つは Microsoft Entra ID(login.microsoftonline.com)の Cookie で、サインインの状態を表し、SSO に使われます。もう1つは、使うサービス(たとえば SharePoint)自身の Cookie です。
| Cookie | 送り先 | 役割(公式ドキュメントより) |
|---|---|---|
| ESTSAUTH | Entra ID | SSO のためのセッション情報。ブラウザを閉じると消える |
| ESTSAUTHPERSISTENT | Entra ID | SSO のためのセッション情報。ブラウザを閉じても残る |
| FedAuth | SharePoint | SharePoint のトップレベルのサイトごとの認証 Cookie |
| rtFA | SharePoint | SharePoint 全体で使う認証 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}'
送り方は同じ 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 を作り直す |
参考にした資料
この記事は、次の標準と公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。
- RFC 9110: HTTP Semantics(IETF):HTTP がステートレスなプロトコルであること
- RFC 6265: HTTP State Management Mechanism(IETF):Set-Cookie と Cookie の例、Secure・HttpOnly・Expires・Max-Age
- RFC 6749: The OAuth 2.0 Authorization Framework(IETF):アクセストークンの2つの作り方、リフレッシュトークン
- RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage(IETF):Bearer トークンの定義、Authorization ヘッダの例、401 の例、TLS 必須・URL に入れない・短い期限
- RFC 7662: OAuth 2.0 Token Introspection(IETF):発行元へトークンの有効性を問い合わせるしくみ
- RFC 7519: JSON Web Token (JWT)(IETF):署名と暗号化、プライバシーにかかわる情報を入れないこと
- Session Management Cheat Sheet(OWASP):セッション ID の作り直し、アイドル・絶対タイムアウト、ログアウト時の無効化、Cookie の属性
- Understanding tokens in Microsoft Entra ID(Microsoft Learn):トークンの種類、盗まれたときのなりすまし、サーバ側のログに残さないこと
- Revoke user access in Microsoft Entra ID(Microsoft Learn):アクセストークンとアプリのセッションの取り消しの違い
- Access tokens in the Microsoft identity platform(Microsoft Learn):アクセストークンの既定の期限(60〜90 分)、jwt.ms でのデコード
- Web browser cookies used in Microsoft Entra authentication(Microsoft Learn):ESTSAUTH・ESTSAUTHPERSISTENT
- SharePoint authentication(Microsoft Learn):FedAuth・rtFA、セッション Cookie と「サインインしたままにする」
- Authentication and authorization in Azure App Service and Azure Functions(Microsoft Learn):サインイン後の認証 Cookie
- How managed identities for Azure resources work with Azure virtual machines・Tutorial: Use a Linux VM/VMSS to access Azure resources(Microsoft Learn):VM の中からトークンをもらう流れ、リクエストと応答の例、JWT のアクセストークン
- Create User(Microsoft Learn):Authorization: Bearer を付けた
POST /users - Authenticating to the REST API(GitHub Docs):Authorization: Bearer でトークンを送ること
- Authentication – Meraki Dashboard API v1(Cisco Meraki Developer Hub):Authorization: Bearer で API キーを送る例、キーの権限と有効期限、発行する画面
- Set-Cookie header(MDN Web Docs):SameSite の値、セッション Cookie
- View, add, edit, and delete cookies(Chrome for Developers):開発者ツールでの Cookie の見方
あとがき
図にしてみると、セッションとトークンの違いは「状態をどこに置くか」に尽きる、というのがいちばんの気づきでした。サーバが覚えているから止めやすいセッションと、サーバが覚えていないから身軽な代わりに止めにくいトークン。アカウントを止めたのにしばらくアクセスできてしまう、という話の理由も、この違いで説明がつきます。
コメント