
「JWTアクセストークンとIDトークンは誰が何のために必要としているのか」というタイトルで DevelopersIO 2026 に登壇しました #devio2026
こんにちは。fannaly事業部で fannaly auth という ID 製品を開発している宮部です。
2026年10月16日に DevelopersIO 2026 Fukuokaに登壇した、「JWTアクセストークンとIDトークンは誰が何のために必要としているのか」についての記事です。2種類のトークンの違いと見極め方について解説します。
登壇資料
セッション概要
2026年10月16日に DevelopersIO 2026 Fukuoka で、JWT のアクセストークンと ID トークンの違いについてお話ししました。
もともと、エンジニアでも ID トークンとアクセストークンの違いを説明できる人が少ないと感じていました。両方が同じ JWT 形式になると、見た目まで似てくるのでさらに区別がつかなくなるだろうとも考えていました。
そして、クライアントがトークンを取り違えて送信してきたケースで、受け取った JWT だけで ID トークンなのかアクセストークンなのかを常に正しく判別することができるのかと考えた時に「案外これは難しいのでは?」となったのが、この話をしようと思った経緯です。
そこで、それぞれのトークンは誰が何のために必要としているのか、受け取った側はどう見分ければいいのかを、RFC と OpenID Connect の仕様をもとに整理しました。
どっちがどのトークンの中身?

以下は JWT 型アクセストークンと ID トークンをデコードした時のヘッダとペイロードの例です。
A
// ヘッダ
{ "alg": "RS256", "typ": "JWT" }
// ペイロード
{
"iss": "https://as.example.com",
"sub": "5ba552d67",
"aud": "s6BhdRkqt3",
"exp": 1639528912,
"iat": 1618354090,
"jti": "9f8e7d6c5b4a",
"nonce": "n-0S6_WzA2Mj"
}
B
// ヘッダ
{ "alg": "RS256", "typ": "at+jwt" }
// ペイロード
{
"iss": "https://as.example.com",
"sub": "5ba552d67",
"aud": "https://api.example.com",
"client_id": "s6BhdRkqt3",
"exp": 1639528912,
"iat": 1618354090,
"jti": "9f8e7d6c5b4a",
"scope": "orders:read"
}
iss、sub、exp、iat は両方にあり、ぱっと見ではほとんど区別がつきません。
今回はこの2つをベースに見分けられるようになっていきましょう。
仕様自身が「似ている」と言っている

実際似ているのは JWT アクセストークンの仕様である RFC 9068 でも書かれていることであり、私の記載している例が意地悪なわけではありません。
The JWT access token data layout described here is very similar to that of the id_token as defined by [OpenID.Core].
そもそも、アクセストークンが JWT であること自体は仕様の要求ではありません。
OAuth 2.0 の RFC 6749 はアクセストークンの形式を決めていません(RFC 6749 Section 1.4)。
実際によく使われるのは opaque トークンと JWT の2つです。
JWT にする利点

では、JWTにする利点について考えていきましょう。
認可サーバーからアクセストークンを受け取ったクライアントが、リソースサーバーと通信するケースで考えます。
opaque トークンの場合
- 設定情報を取得
- 認可サーバーからアクセストークンをクライアントが受け取る
- クライアントがアクセストークンを Authorization ヘッダに付与しリソースサーバーにアクセス
- リソースサーバーはトークンを受け取ると認可サーバーへイントロスペクション(RFC 7662)で問い合わせ、有効か、誰のトークンか、どのスコープかを検証
- 正しい場合、クライアントに対してリソースを返す
JWT トークンの場合
- 設定情報を取得
- 公開鍵を取得(JWKS)
- 認可サーバーからアクセストークンをクライアントが受け取る
- クライアントがアクセストークンを Authorization ヘッダに付与しリソースサーバーにアクセス
- リソースサーバーはトークンを受け取ると、署名を検証
- 有効か、誰のトークンか、どのスコープかを検証
- 正しい場合、クライアントに対してリソースを返す
以上のように JWT にすると、リソースサーバーはトークンを受け取るたびに認可サーバーへ問い合わせる必要がなくなります。[1]
RFC 9068 の Introduction でも、認可サーバーを介さずにリソースサーバーが直接パースして検証できる形式で発行する実装が多い、という背景が書かれています(RFC 9068 Section 1)。
逆に opaque の場合はランダムな文字列でありクライアントもリソースサーバーも中身を読むということ自体ができないため、ID トークンと取り違えることはそもそもないでしょう。
aud が違う場合がある

先ほどの答えは、A が ID トークン、B がアクセストークンです。
一番わかりやすい違いは aud です。ID トークンの aud はクライアントID で、これは OpenID Connect Core の決まりです(OpenID Connect Core 1.0 Section 2)。アクセストークンの aud は RFC 9068 ではリソースサーバーの識別子になります(RFC 9068 Section 3)。
ただし「場合がある」としたのは、アクセストークンの aud にもクライアントID を入れているケースがあるためです。
その場合、aud だけでは見分けられません。
aud が違うということは宛先が違う

aud は audience の略であり、「このトークンは誰宛てか」を表します。
ID トークンは認可サーバーからクライアントへ発行され、受け取るのも読むのもクライアントであり、認証のためのトークンです。
アクセストークンもクライアントが受け取りますが、読むのはリソースサーバー、クライアントは運ぶだけの認可のためのトークンです。
手紙に例えるなら同じ JWT という見た目の封筒に入っていますが、開けていいのは自分宛てのものだけですよね。
クライアントは読まずに送るだけ

RFC 9068 は、クライアントがアクセストークンの中身を見ることをはっきり禁止しています。
The client MUST NOT inspect the content of the access token
理由も同じ Section に書かれていて、認可サーバーとリソースサーバーはいつでもトークンの形式を変えうる(たとえば opaque に切り替える)ので、中身を読むクライアントのロジックは壊れる可能性があります。
また、アクセストークンには個人情報が入っていることもあります。
JWT は鍵がなくても誰でもデコードできるので、クライアントで読んでログや画面に出してしまえばプライバシー上の問題に繋がります。
クライアントは中身を見ずに Authorization: Bearer ヘッダに載せてリソースサーバーへ送るだけです。
中身を見て検証するのは、受け取ったリソースサーバーの役割です。
トークンを受け取った側はどう見分ける?

同じ認可サーバーが発行していれば、ID トークンもアクセストークンも、多くの場合は同じ JWKS で公開されている同じ鍵で署名されています。そのため、署名検証だけでは区別できません。
リソースサーバーが署名検証が通るだけでトークンを受け入れていると ID トークンも受け入れてしまいます。
ID トークンはフロントエンドに置かれることも多く、アクセストークンより保護が緩いことが多いです。
逆向きもあります。認可サーバーは id_token_hint で ID トークンを受け取るケースがありますが、ここにアクセストークンを入れられても署名検証は通ります。しかも OpenID Connect Core では、id_token_hint として使う場合は認可サーバーが aud に入っていなくてもいいことになっているので、aud で弾けるとは限りません。
The Authorization Server need not be listed as an audience of the ID Token when it is used as an id_token_hint value.
OpenID Connect Core 1.0 Section 3.1.2.1
RFC 8725 ではこういう取り違えを Cross-JWT Confusion と呼んでいます(RFC 8725 Section 2.8)。
typ ヘッダで見分ける

最初のスライドでヘッダも載せていたのはこのためで、ID トークンの typ は JWT、アクセストークンの typ は at+jwt でした。
RFC 9068 では、リソースサーバーの検証手順の最初が typ の確認です。
The resource server MUST verify that the "typ" header value is "at+jwt" or "application/at+jwt" and reject tokens carrying any other value.
認可サーバー側も、id_token_hint に at+jwt や application/at+jwt のトークンが来たら拒否できます。
署名が通ったかどうかと、それが何のトークンなのかは別の話です。
typ は絶対にあるのか

しかし、typ で全て解決とは言い切れません。
RFC 9068 は 2021年10月発行で、OAuth 2.0(RFC 6749、2012年10月)から9年後にできた比較的新しい仕様です。
それ以前から JWT のアクセストークンを発行していた実装は、それぞれ独自の形式であり互換性のために維持していることも多いでしょう。
主要な IDaaS の公式ドキュメント(2026年10月時点)を見ると、次のようになっています。
- Microsoft Entra ID: 自分で登録した API 向けのアクセストークンは JWT で、
typはJWT。 - Okta: カスタム認可サーバーのアクセストークンは JWT で、ヘッダに
typがない。 - Auth0: API 向けのアクセストークンは JWT で、デフォルトのプロファイルなら
typはJWT。RFC 9068 プロファイルを選んだときだけat+jwt。 - ID トークン: OpenID Connect Core は
typを定めていない(歴史的にJWTの場合が多い)
実は JWT が RFC として標準化するより前に OpenID Connect で ID トークンとして採用されていた歴史があります。
RFC 7519 Section 5.1 では typ ヘッダを利用するなら JWT を指定することが推奨されており、ID トークンもアクセストークンも typ が JWT、ということは普通に起こります。
typ に頼れないときは、検証ルールを排他的にします。
If more than one kind of JWT can be issued by the same issuer, the validation rules for those JWTs MUST be written such that they are mutually exclusive, rejecting JWTs of the wrong kind.
たとえば、aud が自分の識別子か、片方のトークンにしか存在しないクレームがあるか、で判定します。
片方にしか存在しないクレームとは

ではどのクレームが片方にしかないのかと言われれば、それは実装次第です。
たとえば jti は ID トークンに入っていても仕様違反ではありません。
client_id もアクセストークンにしかないように見えますが、Entra ID のアクセストークンは client_id ではなく azp を使っています。
クレームで区別するための標準仕様はありません。ID トークンは OpenID Connect Discovery で claims_supported が公開されていますが(OpenID Connect Discovery 1.0)、アクセストークンにはそれに相当する仕様がないので、各サービスの公開資料などで見比べることになります。
「認可のためのアクセストークンなら、認証の情報は入っていないのでは?」と思うかもしれませんが、そうとも言い切れません。
RFC 9068 は、auth_time、acr、amr といった認証に関するクレームをアクセストークンに入れることを OPTIONAL で認めています(RFC 9068 Section 2.2.1)。
結局は、受け取るトークンを実際に見て決めることになります。
最後に

セッションでお話しした内容は、次のとおりです。
- 似た見た目の JWT でも、ID トークンはクライアント宛て、アクセストークンはリソースサーバー宛てで、宛先が違う
- クライアントはアクセストークンを読まずに渡す
- 受け取った側は
typで、typがなければaudや片方にしかないクレームで判別する - ドキュメントをちゃんと読んで実装しよう
- クライアントは正しいトークンを送ろう
署名検証が通ることと、自分宛てのトークンであることはイコールではありません。
JWT を受け取る側を実装するときは、何のトークンを受け取るつもりなのかを一度確認してみてください。
参考文献
- RFC 9068 JWT Profile for OAuth 2.0 Access Tokens
- RFC 6749 The OAuth 2.0 Authorization Framework
- RFC 7517 JSON Web Key (JWK)
- RFC 7519 JSON Web Token (JWT)
- RFC 7662 OAuth 2.0 Token Introspection
- RFC 8414 OAuth 2.0 Authorization Server Metadata
- RFC 8725 JSON Web Token Best Current Practices
- OpenID Connect Core 1.0
- OpenID Connect Discovery 1.0
- Microsoft Entra ID Access token claims reference
- Okta The typ field is missing from the access token header
- Auth0 Access Token Profiles
実際には、認可サーバーの設定情報(JWKS の URL などが書かれている)や公開鍵(JWKS、RFC 7517 Section 5)は取得してキャッシュしておき、署名の検証はリソースサーバーの中で済ませます。設定情報は
/.well-known/openid-configuration(OpenID Connect Discovery 1.0 Section 4)または/.well-known/oauth-authorization-server(RFC 8414 Section 3)から取得します。キャッシュの期限切れや鍵のローテーションのときには取り直すので、認可サーバーへの問い合わせが完全になくなるわけではありません。 ↩︎









