OAuth のアクセストークン※１の実装方法は認可サーバーの実装依存です。

実装依存ではありますが、RFC 6749 の『1.4. Access Token』にある次の記述が示唆するように、

The token may denote an identifier used to retrieve the authorization information or may self-contain the authorization information in a verifiable manner (i.e., a token string consisting of some data and a signature). ...

アクセストークンの実装方法はおおむね『識別子型』と『内包型』の二つに大別することができます。そして、これらを組み合わせる『ハイブリッド型』の実装もあります。

※１：アクセストークンの概念については『一番分かりやすい OAuth の説明』を参照してください。

識別子型の実装方法では、アクセストークンに紐付く情報を認可サーバーのデータベース内に保存します。そして、そのデータレコードを一意に特定できる識別子をアクセストークンとします※２。

※２：実際の実装では、データレコードにはアクセストークンそのものではなくアクセストークンのハッシュ値を保存することになると思います。

一方、内包型の実装方法では、アクセストークンに紐付く情報をアクセストークン自体の中に埋め込みます。

ハイブリッド型の実装方法では、内包型アクセストークンを生成しつつも、それに付随するデータを認可サーバーのデータベース内に持ちます。

アクセストークンが識別子型の場合、アクセストークンの情報を取得するために、リソースサーバーは認可サーバーに問い合わせをしなければなりません※３。問い合わせ用に認可サーバーが用意する API はイントロスペクション API（Introspection API）と呼ばれます。

イントロスペクション API には、RFC 7662 という標準仕様が存在します。

※３：認可サーバーとリソースサーバーがデータベースを共有しているシステムでは、リソースサーバーはアクセストークンの情報を取得するために直接データベースを参照します。

アクセストークンが内包型の場合、アクセストークンの中身を読めば、アクセストークンに紐付く情報（例えば「有効期限」）を取得することができます。

内包型アクセストークンは、暗号化されていない限り、そのフォーマットは公知となってしまいます。そのため、何かしら工夫をしなければ、簡単に偽造されてしまいます。

リソースサーバーは、受け取ったアクセストークンに埋め込まれている情報を鵜呑みにする前に、そのアクセストークンが偽造されたものではないことを何かしらの方法で検証しなければなりません。前掲した RFC 6749 の『1.4. Access Token』からの引用部に “in a verifiable manner” と書いてあるのはそれが理由です。

偽造されていないことを検出する一般的な方法は、生成したデータに署名をつけ、データ利用時にその署名を検証する方法です。内包型アクセストークンの場合でいうと、認可サーバーが署名付きアクセストークンを生成し、リソースサーバーがその署名を検証する、という手順となります。

署名付きデータの汎用形式として、RFC 7519 で規定される JWT（JSON Web Token）の使い勝手が良いため、内包型アクセストークンのフォーマットとして JWT が選ばれることが多いです。実際、それを前提としている記述を含む仕様書も存在します。

アクセストークンの実装方法として JWT を採用する場合に考慮すべき点を挙げます。

署名アルゴリズムの選択肢は RFC 7518 の『3.1. "alg" (Algorithm) Header Parameter Values for JWS』に列挙されているものになります。ただし、『署名無し』を意味する none は選択肢から外れます。

HS256 、 HS384 、 HS512 は対称鍵系アルゴリズムなので、認可サーバー（JWT 型アクセストークンを生成する側）とリソースサーバー（JWT 型アクセストークンを解釈する側）が同じ鍵を共有する必要があります。この共有鍵の決め方を定めている標準仕様は現在のところ存在しません。

『OpenID Connect Core 1.0』の『10.1. Signing』には、「ID トークンやリクエストオブジェクトなどの署名に対称鍵系アルゴリズムを用いる場合、クライアントシークレットを鍵として用いること」という旨の規程があります。しかしこの規程は、認可サーバーとクライアントの間でしか成立しないので、認可サーバーとリソースサーバーの間で共有する鍵の決め方に流用することはできません。

上記のことから、署名アルゴリズムに対称鍵系を用いる場合、共有鍵の決め方については実装者が独自に定める必要があります。

他のアルゴリズムは非対称鍵系アルゴリズムになります。

認可サーバーは秘密鍵を用いてアクセストークンに署名し、リソースサーバーは認可サーバーの公開鍵を用いて署名を検証します。必然的に、署名検証に先立ち、リソースサーバーは何らかの方法で認可サーバーの公開鍵を取得しておく必要があります。

認可サーバーが自身の JWK Set（RFC 7517）を公開するエンドポイントを提供し、その JWK Set の中にアクセストークンの署名を検証するための公開鍵を含めているのであれば、リソースサーバーはそのエンドポイントから署名検証用の公開鍵をダウンロードすることができます。

JWK Set を公開するエンドポイントの URL については、もしも認可サーバーが『OpenID Connect Discovery 1.0』をサポートしているのであれば、その認可サーバーのディスカバリーエンドポイント（ 認可サーバーの識別子/.well-known/openid-configuration ）から情報を取得することができます。ディスカバリーエンドポイントは、サーバーの設定情報を JSON 形式で返します。その JSON の中にある jwks_uri というパラメーターの値が JWK Set を公開するエンドポイントの URL を示しています。（例：Google のディスカバリーエンドポイント）

RFC 7518 では RS256 を Recommended（推奨）としていますが、非対称鍵系アルゴリズムのうち RS で始まるものの使用は避けたほうがよいでしょう。セキュリティー上の懸念があることから、『Financial-grade API - Part 2: Read and Write API Security Profile』の『8.6. JWS algorithm considerations』では、 RS 系アルゴリズムを使うべきではないとしています。また、鍵のサイズやパフォーマンスの観点からも、他のアルゴリズムのほうが好ましいです。

RFC 7516 を用いて、JWT 型アクセストークンを暗号化することは可能です。

認可サーバーとリソースサーバーの間で共有する鍵の決め方については、対称鍵系署名アルゴリズムと同様、仕様は存在しないので、実装者が独自に定める必要があります。

暗号化に非対称鍵系アルゴリズムを用いる場合、認可サーバーは、リソースサーバーの公開鍵を使って暗号処理をすることになります。ただし、リソースサーバーの公開鍵の取得方法については、標準仕様は存在しません※４。そのため、リソースサーバーの公開鍵を認可サーバーに渡す方法については、実装者が独自に定める必要があります。

アクセストークンの暗号化に非対称鍵系アルゴリズムを用いる場合、認可サーバーのイントロスペクションエンドポイントの実装では、そのアクセストークンを復号するためにリソースサーバーの秘密鍵が必要となります。これを問題と考えるならば、イントロスペクションエンドポイントの実装の選択肢は、（１）暗号化されたアクセストークンが渡されてきた場合はエラーを返す、（２）そもそもイントロスペクションエンドポイントを提供しない、のどちらかになります。

※４：リソースサーバーのメタデータを定める仕様（OAuth 2.0 Protected Resource Metadata）が提案され、メタデータの一つとして jwks_uri が含まれていましたが、当ドラフトの最終更新日は 2 年以上前の 2017 年 1 月 19 日であり、ドラフト自体は既に無効となっています。

暗号化していない JWT 型アクセストークンの場合、ペイロード部分は簡単に読めてしまいます。そのため、クライアントに知られたくない情報をアクセストークン内に含めてはいけません。

クライアントに知られたくない情報をアクセストークンに紐付けたい場合、次のいずれかの方法をとることになると思います。

不可能ではないですが、内包型アクセストークンの失効は難しいです。構造的には PKI 証明書と同じなので、期限切れ前に内包型アクセストークンを失効させるためには、PKI の CRL（Certificate Revocation List）や OCSP（Online Certificate Status Protocol）相当の仕組みを運用するか、もしくは署名検証用の鍵自体を失効しなければなりません。ただし後者の方法は他のアクセストークンも一緒に失効されてしまいます。

CRL や OCSP 相当の仕組みを構築するためには、まず、アクセストークンを一意に識別できるようにする必要があります。これは、JWT の jti クレームを使えば実現できます。そして、アクセストークンを失効させるたびに、その一意識別子を「失効されたアクセストークンのリスト」に追加します。その一意識別子は、当該アクセストークンの元々の有効期限が切れるまでは失効リスト内に保持しておかなければなりません。

アクセストークンを受け取ったリソースサーバーは、そのアクセストークンの失効状態を確認しなければなりません。CRL 相当の仕組みであれば、どこかから失効されたアクセストークンのリストをダウンロードし、その中に受け取ったアクセストークンの一意識別子が含まれているかどうかを確認することになります。一方、OCSP 相当の仕組みであれば、OCSP レスポンダに相当する「アクセストークンの失効状態を返す API」に、受け取ったアクセストークンの一意識別子を渡して失効状態を教えてもらうことになります。

しかしながら、アクセストークンの失効状態を確認するために認可サーバーに問い合わせをすると、識別子型アクセストークンにおいてイントロスペクション API に問い合わせをするのと同様に、ネットワーク通信が発生してしまいます。これでは、内包型アクセストークンの最大の利点が損なわれてしまいます。識別子型アクセストークンのほうが利点が多いことを考慮すると、内包型アクセストークンを積極的に採用する理由がほとんどなくなってしまいます※５。

そのため、内包型アクセストークンを採用する場合は、「アクセストークンの有効期間を短くし、失効を諦める」という妥協をすることが多くなると思われます。

※５：これを差し置いても内包型アクセストークンを採用しなければならない理由があるとすれば、それは「何らかの事情によりリソースサーバーと認可サーバーが通信できない」場合だと思われます。

アクセストークンの情報、例えば、スコープ、有効期限、クライアント ID、などをどのようにペイロード部に格納するかについては、現在のところ標準仕様はありません※６。

例えば、アクセストークンに紐付くスコープ群の表し方としては、（１）クレーム名を scopes とし、その値を配列にする、

という方法や、（２）クレーム名を scope とし、スコープ名をスペースで連結した単一文字列にする、

といった方法など、幾つか考案することができます。無難な選択をするのであれば、RFC 7662 や RFC 7519 で定められているクレーム名と型に従うのがよいでしょう。

※６：第四回 OAuth Security Workshop で提案がありましたが、一見して考慮不足が明らかで参加者からだいぶ叩かれたようです（たたき台だけに）。仕様書形式のドラフトはこちら。

『OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens』（以降 MTLS）という仕様書には、トークンリクエストの際にクライアントが提示したクライアント証明書を、発行するアクセストークンと紐付ける仕様が含まれています（参考：『クライアント証明書に紐付くトークン』）。

アクセストークンがクライアント証明書と紐付いており、かつ、アクセストークンが JWT で表現されている場合、その JWT はそのクライアント証明書のハッシュ値をペイロード部に含めるべき、と当仕様書は述べています。具体的には、クライアント証明書の X.509 Certificate SHA-256 Thumbprint を x5t#S256 という名前で、 cnf クレーム（参考：RFC 7800）内に含めるべきと述べています。

下記は当仕様書の『3.1. JWT Certificate Thumbprint Confirmation Method』から抜粋した例です。

cnf クレームは、JWT 形式のアクセストークンのペイロード部で用いるクレームの名前について具体的に定めている例となります。

『OpenID Connect Core 1.0』の『5.3. UserInfo Endpoint』では、ユーザー情報エンドポイントというエンドポイントが定義されています。このエンドポイントに openid スコープを含むアクセストークンを渡すと、当該アクセストークンに紐付くユーザーのユーザー情報が返ってきます。

ユーザー情報エンドポイントから返されるユーザー情報に含めて欲しいクレーム群は、認可リクエスト時に要求することができます。要求方法には『5.4. Requesting Claims using Scope Values』で定義される方法と『5.5. Requesting Claims using the "claims" Request Parameter』で定義される方法の二つがあります。

ここで重要なのは、「ユーザー情報エンドポイントからの応答に含めるべきクレーム群をアクセストークンに紐付けて覚えておかなければならない」という点です。論理的には、認可サーバー側に対応するデータレコードを持たない内包型アクセストークンでは、その内部に「ユーザー情報エンドポイントからの応答に含めるべきクレーム群」の情報を含んでいなければならないということです。

例えば、認可リクエストの scope パラメーターの値が openid phone であり、 claims パラメーターの値が次の JSON であった場合、

結果として生成されるアクセストークンは、下記の例における userinfo と同等の情報を含んでいる必要があります※７。

※７： scope に phone を含めることは、 phone_number クレームと phone_number_verified クレームを要求することと同じです。詳細は『5.4. Requesting Claims using Scope Values』を参照してください。

論理的な結論は、「内包型アクセストークンは、ユーザー情報エンドポイントから返してほしいと要求されたクレーム群に関する情報を内包しなければならない」ということになりますが、この結論について、Authlete 社内の議論が興味深かったので紹介します。下記の会話で justin は『OAuth 2 in Action』（邦訳：OAuth 徹底入門）の著者の Justin Richer で、taka は私です。

justin> Your conclusion on JWt-based access token is incomplete. You do not need to put the user info into the access token, nor should you do so as it is potentially privacy-leaking. The userinfo endpoint will need to dereference the JWT to determine which user it applies to. This can be done with the sub claim. If the claims parameter is to be supported directly as you describe above, then that can be looked up underneath the jti claim which is transaction-specific as Joseph said. No JWT based system is fully self-contained, that I’ve seen. Any those that come closest use encrypted tokens (and the associated key management) to keep information relatively safe.

taka> Privacy-leaking only if claims in claims contain value or values .

justin> Not really — the fact that a field exists at all could be privacy leaking. This is less of a problem with common things like “address” and “family name” but more of an issue once you get to other resource types like medical and financial records.

taka> "No JWT based system is fully self-contained" is an interesting statement.

justin> You are right in the common case, but as Authlete is flexible enough we should consider other things

justin> re: self-contained, what I mean by that is that inevitably the token will be used to look up something in a database someplace

taka> Yes, I understand.

justin> :nod: ok. There is a design pressure, that I’ve seen, to put as much information into the JWT itself so as to minimize lookup and network calls. This is a dangerous pattern with often unintended consequences in security and privacy because you have an all-powerful artifact that leaks everywhere it’s used.

justin> We saw this happen with SAML

justin> Since OAuth is much more about API access we see it less, but it still can creep in

taka> So, Authlete can embed information equivalent to the userinfo property into a JWT-based access token but should dare not to do it. Thank you for your valuable comment.

justin> Yes, it would be technically feasible but I would not recommend it as either an implementation or a general pattern.