Help us understand the problem. What is going on with this article?

IETF OAuth WGの仕様全部見る - 2019/10

More than 1 year has passed since last update.

目的

OAuth 2.0の仕様について

  • "RFC6749"
  • "RFC6750"

の2つです。という紹介がされることがよくあります。

しかし、このRFCが策定された IETF OAuth WG には他にもたくさんの策定済み、策定中の仕様が存在します。

実際のサービスにOAuth 2.0を導入する際、扱うデータやアプリケーションの動作環境などにより、別のRFCの仕様を参照、導入する機会も発生するでしょう。例えば最近聞くことの多くなった PKCE と呼ばれる拡張仕様も RFC7636 です。

様々な仕様を「完全に理解した -> 全然わからん」の繰り返しで調べていっても途中で幽体離脱、もしくはいわゆる "RFC疲れ" という重篤な症状に陥る可能性もありますし、それを乗り越えられたとしてもプロフェッショナル達の「RFC番号が飛び交う会話」についていくためにはさらなる鍛錬を必要とするでしょう。

このような現状を少しでも改善するため、2019年10月23日現在のIETF OAuth WGにて策定済み、策定中の仕様をほぼ全部ざっくりと解説していきます。
自分の他の投稿やブログ、他の方の投稿で触れられた仕様もありますが、細かく誘導はせずに一旦こんな感じの仕様だよというところを残しておきます。

仕様 is どこ?

Oauth Status Pages

こんな風になってます。

スクリーンショット 2019-10-19 4.25.58.png

色々ありますが、雑に整理すると、PublishedってのがいわゆるRFCになったやつです。
IESGなんちゃらとRFC-Editor's なんちゃらってのはWGでの議論が進んでRFC化待ったなし、他のは熱い議論が進められてドラフトといった感じです。

他に draft-ietf-xxx じゃなく draft-(名前)-xxx みたいな仕様もあり、世の中にはこのレベルから拾っていく強者もいます。しかし、手練れ以外は簡単に手を出すなってじっちゃんが言ってた。ので今回は扱いません

ということで、現状でRFC化されたやつ、されてないやつで区切って見ていって、次回以降(があるなら)その差分を見ていくことにします。

ちなみに eyJから始まる〜!!! でおなじみ JSON Web なんとかは別のWGだったりします

Published

まずはRFCになっちゃってる奴から。

RFC6749 The OAuth 2.0 Authorization Framework 日本語訳

OAuth 2.0 のコアな仕様です。

「OAuth とはなんぞや」というところから、「Access Token の渡し方」「Access Token を用いたリソースアクセス」のところまで書かれています。

  • 概要、登場人物、用語
  • Client について、種類や識別子、認証方式
  • プロトコルに登場するエンドポイント
  • アクセス許可を与えるためのフロー
  • Access Token の発行、更新、リソースアクセスの部分
  • 様々な拡張について

この仕様についてはたくさんの解説記事や書籍もありますが、この仕様だけ見たら迷わずに AuthZ Server / Client 実装ができるかというと、そうでもないでしょう。

「Implicit Grant と Resource Onwer Password Credential Grantには気をつけろ。」

RFC6750 The OAuth 2.0 Authorization Framework: Bearer Token Usage

RFC6749では様々な種類の Access Token をサポートできるようになっており、シンプルな Bearer Token(ベアラートークン) という種類のトークンの扱い方がこの RFC6750 にて定義されています。

「電車の切符」などに例えられ「持っていることでアクセス許可されたものとみなせる」このトークンの存在もOAuth 2.0の主な特徴として挙げられます。

  • Bearer Token を用いたリクエスト方法(ヘッダ, フォームデータ, クエリ)
  • "WWW-Authenticate" ヘッダに含まれるレスポンスの形式、エラーコード
  • RFC6749 のフローでも定義されている Authorization Server から Bearer Token 形式の Access Token を受け取る方法

一般開発者向けにAPIを提供しているサービスについて、昔は独自に X-なんたら ヘッダみたいなのを使っていたところが多かった気がしますが、最近はこの Bearer Token を利用しているところも増えた印象です。

「「AUthorization: Beader xxx」という HTTP Header の指定方法は基本なので覚えましょう」

RFC6819 OAuth 2.0 Threat Model and Security Considerations

RFC6749 / RFC6750 にも Security Consideration にも大事なことが書かれていますが、これはより具体的な脅威と対策をまとめたドキュメントです。

  • 前提としている環境、攻撃者、アプリケーション、Client
  • 既にRFC6749 / 6750に組み込まれているセキュリティ機能
  • 脅威モデル
    • Client向け
    • AuthZ Endpoint
    • Token Endpoint
    • 各フロー
    • Access Token 更新
    • リソースアクセス
  • 上記脅威への対策
    • 全体
    • AuthZ Server
    • Client Application
    • Resource Server
    • その他

最初の方は用語の整理のような感じもあるので、これで比較的安全な設計/実装が可能になるのではないかと(当時は)思っていました。
最近は想定されるアプリケーションの変化に伴い個別にNative App/Browser Based App向けにベストプラクティス的な仕様が策定されています。

「これクッソ長いぞ。ランチ後に読んだら意識持ってかれるやつだ。覚悟しろよ。」

RFC8414 OAuth 2.0 Authorization Server Metadata

本投稿で紹介しているように、OAuth 2.0 にはコアとなる仕様と拡張仕様群があります。
Client を実装しようと思った時に、開発者は AuthZ Server の仕様が書かれたドキュメントを読んで各種エンドポイントなどの情報を取得する必要があります。

この仕様では AuthZ Server が自らの識別子、提供している各種エンドポイント、サポートしている Client 認証方式やパラメータと言ったメタデータを JSON 形式で提供する仕組みが定義されています。

# リクエスト
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: example.com
# レスポンス
HTTP/1.1 200 OK
Content-Type: application/json

{
 "issuer":
  "https://server.example.com",
 "authorization_endpoint":
  "https://server.example.com/authorize",
 "token_endpoint":
  "https://server.example.com/token",
 "token_endpoint_auth_methods_supported":
  ["client_secret_basic", "private_key_jwt"],
 "token_endpoint_auth_signing_alg_values_supported":
  ["RS256", "ES256"],
 "userinfo_endpoint":
  "https://server.example.com/userinfo",
 "jwks_uri":
  "https://server.example.com/jwks.json",
 "registration_endpoint":
  "https://server.example.com/register",
 "scopes_supported":
  ["openid", "profile", "email", "address",
   "phone", "offline_access"],
 "response_types_supported":
  ["code", "code token"],
   "service_documentation":
 "http://server.example.com/service_documentation.html",
  "ui_locales_supported":
   ["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
}

この後に紹介していく拡張機能の仕様では、この "AuthZ Server Metadata" で表現されている機能を拡張するものもあります。
その場合、このメタデータにどのような値を追加するか、というところもそれぞれの仕様に含まれます。

ある時点での AuthZ Server の仕様にしたがって Client 実装を行うというユースケースではこの情報をあまり利用する機会はないかもしれませんが、後述する "Dynamic Registration" を用いた Client 設定の自動化などが行われるようになると結構重要になる仕組みでしょう。

「OpenID Connect にもメタデータが取れる仕組みがあるよ」

RFC6755 An IETF URN Sub-Namespace for OAuth

この仕様では RFC6749 のパラメータを拡張したい時などに使われるSub-Namespace(urn:ietf:params:oauth)が定義されています。

「拡張仕様で Token Endpoint に送る grant_type の指定に使われます。」

RFC7636 Proof Key for Code Exchange by OAuth Public Clients

Public Client から AuthZ Code Grant を利用する際、Client Secret を安全に保持できないために(カスタムURIスキームの重複の扱いなどによって) AuthZ Code を取得されることで Access Token まで取得されてしまうリスクがあります。

この仕様では AuthZ Code Grant の Access Token 取得までの一連の流れに AuthZ Code 交換用のキーである "Code Verifier" を利用することで攻撃を緩和させるための方法が定義されています。

  • プロトコル概要
    • Clien tは Code Verifier, Code Challenge を作成
    • Client は Code Challenge を認可リクエストに含んで送信
    • AuthZ Serverは Code Challenge と紐付けた AuthZ Code を返却
    • Client は AuthZ Code と Code Verifier を AuthZ Server の Token Endpoint に送信
    • AuthZ Server は Access Token 返却前に Code Verifier を検証
  • Security Consideration

Client は AuthZ Request を送る前に Code Verifier(Code Challenge) と自身のセッションを紐づけます。
AuthZ Server は AuthZ Request に含まれた Code Challenge と AuthZ Code を紐づけます。よって、セッションと AuthZ Code が紐づけられ、それを認可サーバーが検証します。
AuthZ Code が第3者の手に渡ったとしても、Code Verifier を知らなければ Access Token などの取得はできません。

鍵を一つだけセッションに保持するだけでセッションと AuthZ Code の紐づけが可能となるため、Client Secretを安全に保持できる Confidential Client でも導入するメリットはあります。
OAuth 2.0のコア仕様と合わせてもはや標準装備ぐらいの感覚で覚えておくのが良いでしょう。

「Client が毎回同じ OAuth Verifier を送って来たら意味ないのでそこだけちゃんとやれ。」

RFC7009 OAuth 2.0 Token Revocation

Client が Access Token を取得後、どのように扱うかについては様々です。

  • 永続化されたストレージに保存して非同期でAPIアクセスを行うようなユースケースの場合、Client は Access Token が有効な限り利用し続ける
  • Access Token をログインセッションに紐づけ、ログアウト時にセッションとの紐づけを破棄する

後者の場合、Client から AuthZ Server に対して Access Token / Refresh Token を無効化するリクエストを送り、AuthZ Server 側でも無効化することができればより安全性が高まります。

この仕様では、特定トークンの無効化を要求するためのエンドポイントの追加、リクエスト/レスポンスの形式が定義されています。

  • トークン無効化
    • Client が AuthZ Server に送るリクエスト
    • AuthZ Server からのレスポンス
    • CORSサポートについて

「AuthZ Serverが気合い入れて実装してもClientがちゃんと使わないと無意味。

RFC7662 OAuth 2.0 Token Introspection

OAuth 2.0 の登場人物である AuthZ Server, Resource Server ですが、両者は必ずしも同一のシステムで動作しているとは限りません。
ある程度規模が大きなサービスになると、機能単位でサーバー、ドメインを分けるという設計は珍しいものでもないでしょう。
リソースアクセス時に Resource Server は Client から Access Token を受け取り、Client が自身が提供するリソースへのアクセスを許可されていることを確認しますが、どのように実現するかについては定義されていません。

この仕様では Resource Server が AuthZ Server に Access Token についての問い合わせを行い、AuthZ Server がメタデータを返す方法を定義しています。

  • エンドポイント
    • リクエスト
    • レスポンス

Client から受け取った Access Token を Resource Server が AuthZ Server に問い合わせるための仕様 であり、Client 向けに提供されるエンドポイントおよび機能ではないことに注意が必要です。

似たような用途の仕様として、後述する "JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens" ではJWTを利用して構造化された Access Token を利用する方法が定義されており、仕様策定が進んでいます。
こちらは Resource Server が受け取った JWT 形式の Access Token を検証し、AuthZ Server への問い合わせを行わずにリソースアクセスを提供することも可能ですが、Access Token が無効化されることを考慮すると有効期限を短くするなど、細かい検討が必要です。

この辺りはAPIで提供される機能やシステム全体の負荷なども考慮し、設計していく必要があるでしょう。

「後でこのレスポンスをJWTにする仕様も出て来るぞ」

RFC8628 OAuth 2.0 Device Authorization Grant

OAuth 2.0 の AuthZ Code Grant や Implicit Grant なんてのは、PC/スマートフォンといったユーザーの手元にある端末のブラウザ上で Client から AuthZ Server の AuthZ Endpoint にリダイレクトして...という処理が行われます(いわゆる OAuth Dance)。
しかし、ブラウザを持たないデバイス、テレビなどのように認証処理が困難な場合、RFC6749で定義されているやり方では OAuth Dance を実現できません。

そこで、この仕様ではHTTPSのリクエストが送れ、URLの案内などの対話が可能なデバイスにおいて、手元の端末を用いて OAuth 2.0 のアクセス許可を可能とし、Access Token 等を取得する方法が定義されています。

  • プロトコル
    • Device AuthZ 用リクエスト : RFC6749 の AuthZ Request 相当の情報を送信
    • Device AuthZ 用レスポンス : ユーザーと対話するためのURLなど
    • ユーザーインタラクション : QRコードやURL表示などでユーザーを誘導し、認証やアクセス許可を行う
    • Device Access Token のリクエスト : Token Endpoint にポーリング
    • Device Access Token のレスポンス : ユーザーインタラクションが終わっていたらRFC6749相当のレスポンス

手元の端末でアクセス許可できるのは可能性を感じますね。
比較的新しいRFCなので今後これを用いた製品なども出てきそうです。

「QRコードはURL手打ちみたいなところはUXきついと言われるので導入時によく考慮すべし」

RFC7591 OAuth 2.0 Dynamic Client Registration Protocol

OAuth 2.0 の Client 登録といえば開発者が AuthZ Server を提供するサービス場で静的に登録するイメージが強いでしょう。
この仕様では Dynamic Client Registration つまり、動的に Client を登録する方法を定義しています。

  • Client のメタデータ
  • Client 登録用エンドポイント
    • リクエスト
    • レスポンス
# リクエスト
POST /register HTTP/1.1
Content-Type: application/json
Accept: application/json
Host: server.example.com

{
 "redirect_uris": [
   "https://client.example.org/callback",
   "https://client.example.org/callback2"],
 "client_name": "My Example Client",
 "client_name#ja-Jpan-JP":"\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D",
 "token_endpoint_auth_method": "client_secret_basic",
 "logo_uri": "https://client.example.org/logo.png",
 "jwks_uri": "https://client.example.org/my_public_keys.jwks",
 "example_extension_parameter": "example_value"
}
# レスポンス
HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
  "client_id": "s6BhdRkqt3",
  "client_secret": "cf136dc3c1fc93f31185e5885805d",
  "client_id_issued_at": 2893256800,
  "client_secret_expires_at": 2893276800,
  "redirect_uris": [
    "https://client.example.org/callback",
    "https://client.example.org/callback2"],
  "grant_types": ["authorization_code", "refresh_token"],
  "client_name": "My Example Client",
  "client_name#ja-Jpan-JP":
     "\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D",
  "token_endpoint_auth_method": "client_secret_basic",
  "logo_uri": "https://client.example.org/logo.png",
  "jwks_uri": "https://client.example.org/my_public_keys.jwks",
  "example_extension_parameter": "example_value"
}

開発者のアクションを伴わない動的な登録というとどうしても「緩い」 Client が作成されるようなイメージもあります。
仕様ではあるソフトウェアが "software statement" を含むことで端末ごとやインスタンス単位で Client を登録するようなユースケースも想定されています。

RFC7592 OAuth 2.0 Dynamic Client Registration Management Protocol

RFC7591 で定義されている Client 登録に加え、登録されている情報の参照、更新、削除機能を定義します。

  • エンドポイント
    • 参照リクエスト : GET
    • 更新リクエスト : PUT
    • 削除リクエスト : DELETE

RFC8176 Authentication Method Reference Values

ここでは純粋な OAuth 2.0 の話ではなく OpenID Connect を用いたID連携の話をしましょう。
OAuth 2.0 で言う所の AuthZ Server / Resource Server である Identity Provider からエンドユーザーの情報を受け取るサービスがあり、このサービスが自前でパスワード認証、さらに TOTP や FIDO U2F などの多要素認証を導入しているような場合を考えます。

ID連携における認証強度を足し算で考えてみると

  • Identity Provider上でパスワード認証のみを行なったエンドユーザーに対しては追加で認証したい
  • Identity Provider上でパスワード以外の単一の認証(SMSのみとか)を行なったエンドユーザーに対しては別の認証方式で追加認証を求めたい
  • Identity Provider上でパスワード認証 + α の MFA を行なったエンドユーザーに対してはそのまま通したい

といった判定をしたいと思うこともあるでしょう。
そのためには、ID連携時に 「エンドユーザーがどの認証を済ませてきたのか」 をやりとりする必要があります。
OpenID Connect では、エンドユーザーの認証イベント情報をやりとりするための ID Token の中に "amr(Authentication Methods References)" というクレームを含むことが定義されています。

この仕様では、その "amr" クレームとしてやりとりされる値が定義されています。

  • "face", "fpt", "geo", "hwk", "iris", "kba", "mca", "mfa", "otp", "pin", "pwd", "rba", "retina", "sc", "sms", "swk", "tel", "user", "vbm", "wia"
  • "acr(Authentication Context Class Reference)" クレームとの関係

ここまで紹介しておいてなんですが、IdPとしては具体的な認証方式についてはなるべく渡したくないという気持ちもあるかもしれません。
ID連携しているサービス自体、もしくはこの結果を知る存在が悪意を持ち、 "pwd(=パスワード認証)" で認証済みのユーザーに対してパスワードリスト攻撃を行うこともできます。

認証方式にフォーカスした "amr" に対し、"acr(Authentication Context Class Reference)" は別途定義されたレベルを満たすことを要求したり、満たしていることを応答したりというやりとりに利用できるクレームです。

一般的なC向けのサービスではここまで踏み込むことはほぼないかもしれませんが、今後お金やセンシティブな情報をやりとりする仕組みにID連携が利用されるようになった時、この辺りのクレームの必要となるでしょうし、IdPとしても具体的な認証方式よりもサポートされる可能性が高いかもしれません。

「欲しいけど あげたくはない "amr"」

RFC7521 Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants

OAuth 2.0 をハードに使いこなしていくと、異なるIdentity System(例えば SAML など)との連携をしたくなるかもしれません。

この仕様では Token Endpoint に Access Token を要求する際に下記の部分で別システムのアサーションを利用するための方法が定義されています。

  • Authorization Grant : 別システムのアサーションから Access Token を発行する。RFC6749 でも "Extension Grants" として拡張できることが示されている
  • Client 認証 : 別システムのアサーションを用いて Client の認証を行う

この仕様で定義されているのは

  • アサーションの指定方法 : パラメータなど
    • Authorization Grant : "grant_type" の拡張、"assertion"
    • Client認証 : "client_assertion_type", "client_assertion"
  • アサーションの内容、処理方法 : アサーションにはどのような値が含まれていて、どう検証されるか
  • アサーション適用のシナリオ

といったあたりです。

ただし、この仕様で定義されているものはあくまでもフレームワークであり、この後に続く2つの仕様で具体的な利用方法を定義しています。

  • SAML Bearer Assertion : RFC7522
  • JSON Web Token : RFC7523

個人的に、以下のような仕組みを設計/実装する際にこの仕様を参考にしました。

  • Backend Server が存在する Native App が OAuth 2.0 の Server と Client の立ち位置で実装されてた
  • パスワード認証に ROPC(Resource Owner Password Credentials)が利用されてた (※このあたりの経験から一般論としてROPCをDisり始める事になる)
  • 外部ID連携(ソーシャルログイン)を実装するところでこれが使えそう

Googleアカウントでログインの例でいうと、これは OAuth 2.0(対Backend Server) × OpenID Connect(対Google) となります。
この仕様でいうと、Googleから受け取った ID Token や Authorization Code の値をアサーションとして利用することでソーシャルログインを実装しました。

ということで、割と応用の効くフレームワークだと思います。
サービスが複数のIDシステムの連携などを扱う場合、何か参考になることがあると思います。

RFC7522 Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants

上記アサーションフレームワークのSAML用プロファイルです。

  • "grant_type" : "urn:ietf:params:oauth:grant-type:saml2-bearer"
  • "client_assertion_type" : "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
# Authorization Grant
POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2-bearer&
assertion=PHNhbWxwOl...[omitted for brevity]...ZT4
# Client Authentication
POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Asaml2-bearer&
client_assertion=PHNhbW...[omitted for brevity]...ZT

SAMLのアサーションのどの値を用いて検証するかなども書かれています。

C向けというよりはB向けでSAML使われてるところとOAuth 2.0の組み合わせを考える人とかは、読んでみると良いかもしれません。

RFC7523 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants

上記アサーションフレームワークのJWT用プロファイルです。

  • "grant_type" : "urn:ietf:params:oauth:grant-type:jwt-bearer"
  • "client_assertion_type" : "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
# Authorization Grant
POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
eyJpc3Mi[...omitted for brevity...].
J9l-ZhwP[...omitted for brevity...]
# Client Authentication
POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
client-assertion-type%3Ajwt-bearer&
client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0.
eyJpc3Mi[...omitted for brevity...].
cC4hiUPo[...omitted for brevity...]

RFC7522と同様に、JWTのどの値を検証に用いるかなどが書いてあります。
アサーションの話はここまでにしましょう。

RFC8252 OAuth 2.0 for Native Apps

RFC6749 / 6750 が策定された2012年、世の中では「爆速」「スマホファースト」なるワードが飛び交っていました。
WebApp 中心だった OAuth 1.0 時代とは異なり、OAuth 2.0では船出から「モバイルアプリでOAuthどう実装するの問題」が発生し、そこから数年はカオスな状態が続きました。いや今も続いているのかもしれない。

  • RFC6749 でいうと Implicit Grant なのか
  • Implicit Grant 危ないと誰かが
  • Native App から AuthZ Code Grant 使いたいが Client Secret どうしよう
  • Public Client 向けに Client Secret なしの Client が作れるようになった
  • PKCE登場!これで勝てる?

このRFCは Native App から OAuth 2.0 を利用するにあたってのベストプラクティスがまとめられています。
RFC6749/6819 の想定になかった部分、サポートしきれていない部分も Security Considerations で整理されています。

もちろん Client 側からすると Server がサポートしている機能の範囲内で安全に実装するための方法を探る必要はありますが、これを読んで関連する仕様をみていくという進め方もあるかもしれません。

RFC7519 JSON Web Token (JWT)

いわゆるJWTのHeader/Payloadに含まれるclaim定義です。
これだけは JOSE WG ではなく OAuth WG だったことに最近気づきました。

JSON Web Token...というかよく使われているのは JSON Web Signature かと思いますが、基本的にルールに基づいた署名つきBase64URLエンコードと言えるため、その Payload には自由度があります。
しかし、2つのパーティー間で構造化されたデータをやりとりし、受け取った側が送信元などを検証するためにはこの仕様で定義されている claim の値が必要となります。

OpenID Connect の ID Token やこの投稿で紹介している JWT プロファイルのように、この仕様により定義されているクレームについてはそれを利用、それ以外のものに独自のクレーム名を付与して利用するべきです。

RFC7800 Proof-of-Possession Key Semantics for JSON Web Tokens (JWTs)

RFC7519 で定義されている claim に加えて、この仕様では

  • JWTの送信者が特定の Proof-of-Possession(PoP) Key を所有していること
  • JWTの受信者がそれを暗号技術を用いて確認できること

を実現するために必要な "cnf" claim の値と

  • 非対称な PoP Key
  • 暗号化された対称な PoP Key
  • PoP Key の識別子(kid)
  • PoP Key のURL

をどのように表現するかが定義されています。

    # 非対称な PoP Key
     {
      "iss": "https://server.example.com",
      "aud": "https://client.example.org",
      "exp": 1361398824,
      "cnf":{
        "jwk":{
          "kty": "EC",
          "use": "sig",
          "crv": "P-256",
          "x": "18wHLeIgW9wVN6VD1Txgpqy2LszYkMf6J8njVAibvhM",
          "y": "-V4dS4UaLMgP_4fY4j8ir7cl1TXlFdAgcx55o7TkcSA"
         }
       }
     }

    # 暗号化された対称な PoP Key
    {
      "iss": "https://server.example.com",
      "sub": "24400320",
      "aud": "s6BhdRkqt3",
      "nonce": "n-0S6_WzA2Mj",
      "exp": 1311281970,
      "iat": 1311280970,
      "cnf":{
        "jwe":
          "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhDQkMtSFMyNTYifQ.
          (remainder of JWE omitted for brevity)"
        }
    }

    # PoP Key の識別子(kid)
    {
      "iss": "https://server.example.com",
      "aud": "https://client.example.org",
      "exp": 1361398824,
      "cnf":{
        "kid": "dfd1aa97-6d8d-4575-a0fe-34b96de2bfad"
       }
    }

    # PoP Key のURL 
    {
      "iss": "https://server.example.com",
      "sub": "17760704",
      "aud": "https://client.example.org",
      "exp": 1440804813,
      "cnf":{
        "jku": "https://keys.example.net/pop-keys.json",
        "kid": "2015-08-28"
       }
    }

そもそも、JSON Web Signature(RFC7515) では JWT の検証のために JWT Header に "jwk", "kid", "jku" などを使うことが定義されています。
この仕様では鍵を所有していることを表すために Payload に "cnf" の値として鍵の情報を含みます。
よって、JWTの検証に用いる鍵ではなく、例えば JWT を含むリクエスト自体で暗号を用いた検証をする時にも利用できます。

RFC-Editor's Queue

ここからは RFC 待った無し状態なので、もう見ておいて良いでしょう。

OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens

FAPIのようによりセキュアな要件が求められるユースケースにおいては、トークンを所持しているだけでリソースアクセスを行使しようとしている者がトークンの発行対象者であるとみなす Bearer Token ではなく、トークンの発行対象者が行使していることを検証可能な Sender-constrained Token と呼ばれるトークンが利用される場合があります。

RFC6749 の AuthZ Code Grant でやりとりされる AuthZ Code を用いて Access/Refresh Token を要求する時、Client は Client 認証を求められて AuthZ Server は AuthZ Code と Client の紐付けを検証します。
つまり AuthZ Code は Sender-constrained Token と言えます。

この仕様では、X.509証明書を用いた相互TLS認証をClient認証と Access/Refresh Token に紐付ける方法が定義されています。

  • クライアント証明書について、PKIを用いるものと自己署名ものを両方サポート
  • クライアント証明書を用いた Token Endpoint へのリクエスト時に AuthZ Server が Access/Refresh Token への紐付けを行う
  • Access Token を用いたリソースアクセス時にもクライアント証明書を利用し、Resource Server が検証する
  • JWT形式の Access Token や RFC7662 で定義されている Token Introspection のレスポンスで紐付けられたクライアント証明書を表現するために、RFC7800 の "cnf" claim を利用する
  # クライアント証明書のハッシュ値を含む Payload
  {
    "iss": "https://server.example.com",
    "sub": "ty.webb@example.com",
    "exp": 1493726400,
    "nbf": 1493722800,
    "cnf":{
      "x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
    }
  }

TLS を利用した Sender-constrained Token の実現方法として RFC8471~8473 として策定された Token Binding と比較されることがありますが、Token Binding がHTTP/TLS自体の仕様に手を入れる必要があることから、Client/Serverが相互に仕様に準拠することで実装可能な本仕様の方が導入しやすいと言えます。

既にサポートされているライブラリも存在するので、興味のある方は探してみてください。

Resource Indicators for OAuth 2.0

OAuth 2.0 で Client がアクセスしたいリソースの範囲は scope パラメータとして要求されます。
OpenID Connect では openid という scope を利用していますし、Google などは scope をURL形式になっています。
この scope はリソースアクセスの種類を定義したものであり、AuthZ Server もしくは Resource Server が scope に対応する Resource Server 自体やエンドポイントの組み合わせを持ってアクセスコントロールを実現しているでしょう。

この仕様では、Client が AuthZ Request や Access Token Request に resource パラメータを指定することで直接利用したいリソースを指定する方法が定義されています。

 # AuthZ Request for AuthZ Code Grant
 GET /as/authorization.oauth2?response_type=code
     &client_id=s6BhdRkqt3
     &state=tNwzQ87pC6llebpmac_IDeeq-mCR2wLDYljHUZUAWuI
     &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
     &scope=calendar%20contacts
     &resource=https%3A%2F%2Fcal.example.com%2F
     &resource=https%3A%2F%2Fcontacts.example.com%2F HTTP/1.1
  Host: authorization-server.example.com

# Access Token Request for AuthZ Code Grant
POST /as/token.oauth2 HTTP/1.1
Host: authorization-server.example.com
Authorization: Basic czZCaGRSa3F0Mzpoc3FFelFsVW9IQUU5cHg0RlNyNHlJ
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&code=10esc29BWC2qZB0acc9v8zAv9ltc2pko105tQauZ
&resource=https%3A%2F%2Fcal.example.com%2F 

個人的には scope との違いをもう少し明確に定義する必要がありそうですが、例えば scope と client_id との組み合わせで実際にアクセス可能なリソースが決まる場合などもありそうなので、この方式の方が実装しやすいケースもあるんだろうなと思っています。

OAuth 2.0 Token Exchange

RFC7521~7523 では、Client が JWT や SAML 2.0 のアサーションといったセキュリティトークンを用いた Access Token 要求方法が定義されています。

例えば AuthZ Server / Resource Server がマイクロサービス的な構成になっている場合、Resource Server は自ら受け取った Access Token から別のセキュリティトークンを要求、外部サービスに利用するといったユースケースはありそうです。

この仕様では、"Delegation", "Impersonation" という概念を意識しつつ AuthZ Server にセキュリティトークンを要求、取得する方法、つまりHTTPおよびJSONベースのSecurity Token Service(STS)実装のためのプロトコルが定義されています。

セキュリティトークン発行処理は Token Endpoint で行われます。

# Token Exchange Request
POST /as/token.oauth2 HTTP/1.1
Host: as.example.com
Authorization: Basic cnMwODpsb25nLXNlY3VyZS1yYW5kb20tc2VjcmV0
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange
&resource=https%3A%2F%2Fbackend.example.com%2Fapi
&subject_token=accVkjcJyb4BWCxGsndESCJQbdFMogUC5PbRDqceLTC
&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token
# Token Exchange Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache, no-store

{
 "access_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6IjllciJ9.eyJhdWQiOiJo...y5-kdXjwhw",
 "issued_token_type":"urn:ietf:params:oauth:token-type:access_token",
 "token_type":"Bearer",
 "expires_in":60
} 

特定の用途のための Access Token を要求するということで、先ほど説明した resource パラメータも任意ですが登場します。
セキュリティトークンの発行を柔軟に使うための仕様のため、パラメータの数もそれなりにあります。
比較的規模の大きなマイクロサービスの認証認可基盤みたいなのを設計/実装する際には目を通しておくと参考になるでしょう。

IESG Processing:

これも結構煮詰まってる感じの仕様です。

JWT Response for OAuth Token Introspection

RFC7662 で定義されている Token Introspection API のレスポンスはプレーンなJSONオブジェクトです。
この仕様では Token Instropection API のレスポンスに JWT を利用する方法を定義しています。

AuthZ Server が署名を作成した JWT 形式のレスポンスを返すことで、例えば受け取った Resource Server が Payload に含まれる値を改ざんすることなく別のサービスに送るような場合に受け取った側が検証可能になります。

# Token Introspection Request
POST /introspect HTTP/1.1
Host: server.example.com
Accept: application/jwt
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

token=2YotnFZFEjr1zCsicMWpAA
# Token Introspection Response
HTTP/1.1 200 OK
Content-Type: application/jwt

eyJ0eXAiOiJ0b2tlbi1pbnRyb3NwZWN0aW9uK2p3dCIsImFsZyI6IlJTMjU2In0.eyJ
...
JOd0ziYBpAbEpYGE4p3wog4

## JWT Header
{
 "typ": "token-introspection+jwt",
 "alg": "RS256"
}

JWT Header の typ claim なども独自のものとなるので、Introspection Endpointで作られたものであることを検証できます。
Token Introspection を利用しつつさらに...というユースケースはあまり想像できていないですが、こういうのもあるんだと覚えておくと良いでしょう。

JSON Web Token Best Current Practices

最近は様々なところでJSON Web Token(JWT)が使われるようになりました。
これは JWT を安全に実装して展開していくための実用的なガイダンスを提供することを目的としたベストプラクティスです。

内容は脅威と脆弱性に対するベストプラクティスの紹介という形です。
例えば、昔からある話で JWS の Header を書き換えて alg=none にしたり RS256 を HS256 に変更することでライブラリがその値で検証してしまったりする問題も書いてあります。

他にも色々あるので JWT についての仕様と合わせて読んでみるのも良いでしょう。

The OAuth 2.0 Authorization Framework: JWT Secured Authorization Request(JAR)

RFC6749 では AuthZ Request のパラメータはURLクエリパラメータとして指定され、ブラウザのリダイレクトにより AuthZ Endpoint に送られますが、パラメータの改ざんや置き換え、通信元が認証されていないといった課題があり、それを狙った攻撃も想定されています。

OpenID Connect で採用されている機能ですが、この仕様では JWT(JWS/JWE) 形式の値もしくはそのリファレンスの値を用いてパラメータの整合性、通信元の認証、AuthZ Requet の機密性を保証する方法が定義されています。

  • AuthZ Request
    • Request Object の指定
    • Request Object URI の指定、AuthZ Server のフェッチの方法
  • 検証方法
  • AuthZ Resonse (Error)
# JWTの値を指定
https://server.example.com/authorize?request=eyJ...

# JWTへのリファレンスを指定
https://server.example.com/authorize?
response_type=code%20id_token
&client_id=s6BhdRkqt3
&request_uri=https%3A%2F%2Ftfp.example.org%2Frequest.jwt
%23GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM
&state=af0ifjsldkj

FAPIのように、OAuth で扱う情報がセンシティブになると必須となる拡張仕様です。
ちなみにこの値を Server 側で保持するというやり方も別の仕様で提案されています。

OAuth 2.0 Pushed Authorization Requests

Active

最後は仕様策定真っ只中のドラフトの皆さんです。
詳細はまだこれから更新されていくものだとは思いますが、概要はそんなに変わらないと思うので紹介します。

RFCになってから/なりそうな段階で読むのと、MLやIETFの集まりに参加したりしながら関わっていくのではまた理解の度合いが変わると思います。
最終的に「仕様を書く」レベルまでを見据えるのであれば、その前の段階として「他の人が仕様書いてやっていくのを見る」のも大事かもしれませんね。

「IETFの流れとかは詳しい人紹介するからそっちに聞いてくれ」

JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens

OAuth 2.0 の Access Token について、RFC6749 の仕様では具体的なフォーマットなどが定義されていません。

   Access tokens are credentials used to access protected resources.  An
   access token is a string representing an authorization issued to the
   client.  The string is usually opaque to the client.  Tokens
   represent specific scopes and durations of access, granted by the
   resource owner, and enforced by the resource server and authorization
   server.

   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).  Additional authentication credentials, which are beyond
   the scope of this specification, may be required in order for the
   client to use a token.

この仕様では Access Token に JWT を利用するためのプロファイルが書かれています。
Server の Access Token 設計として、JWT を Access Token に利用しているところもありますが、それぞれが独自に検討したものであり細かい差異が存在します。
この仕様により標準化し、それに沿ったライブラリ、製品が出てくることで相互運用性を高められることを目的としています。

これまで標準化された他のRFCや OpenID Connect の ID Token などのように、rfc7519 で定義されているクレームをベースにして必要だと思われるクレームを追加し、Access Tokenの発行/検証処理が定義されています。
今までなんとなく Access Token に JWT を利用しようかとは思っていた開発者が、より安心して実装できる状態になることに期待ですね。

OAuth 2.0 Incremental Authorization

一昔前の「グロースハック」なテクニックの一つとして、ユーザーから新規登録時に取得する属性情報は最小限とし、必要になったら追加で取得していくべきだ。みたいな考えが広まり、Native Application の権限要求でも同様の考えがあると思います。

OAuth 2.0 を認証に利用しようと思う場合は別として、Client の開発者からすると実装コストという面ではアクセス許可を得るための OAuth Dance の回数は少ないに越したことはありません。
しかし、今後利用する可能性のある AuthZ Request にたくさんの scope を指定して Resource Owner がアクセス許可を要求する画面をみたとき、どう思うでしょうか?
ほとんどの Resource Owner は同意画面を見てないとは思いますが、中には細かく内容を見て「このscopeどこで使うんだよ」と思う人もいます。
かと言って、各処理で必要とする単一のscopeに対して個別に OAuth Dance を行い、各種 Token を管理するというのも複雑になります。

この仕様では、Server が Client - Resource Owner に対して以前要求された scope を保持しつつ新たに要求された scope に対してのアクセス許可を行う方法が定義されています。

と言っても内容を見ると、簡単なパラメータ追加があるのみで後は Server 側で頑張れと言ったところです。
Server の AccessToken 設計によっては既に導入されているところもあるかもしれませんが、標準化されることでより多くの Server でアクセス許可のUXが改善されることを期待したいところです。

Reciprocal OAuth

OAuth 2.0 の Resource Owner, Client, Server(AuthZ/Resource) の3者が登場する方式を 3-legged OAuth などと呼びますが、基本的には Client が Server のリソースにアクセスするための仕組みです。

しかし、実際のサービスでは

  • Client もリソースを持っており、Serverになり得る
  • Server も他のサービスの Client になり得る

ということで、これが重なると

  • ある Resource Owner に対して2つのサービス(サービスA/B)が Client/Server の両方になる

というケースもあるでしょう。このようなケースで仕様に忠実に従うと

  • サービスA -> サービスB に送られてサービスAからのリソースアクセスを許可する
  • サービスB -> サービスA に送られてサービスBからのリソースアクセスを許可する

というように2回 OAuth Dance を踊る必要があります。

この仕様では、Resource Owner がよりシームレスにアクセス許可をできるような拡張仕様が定義されています。

  • Client は通常の OAuth 2.0 のフローを始める
  • Server は Access Token Response(AuthZ Code) or AuthZ Response(Implicit)に "reciprocal" パラメータで必要なリソースを指定する(Reciprocal Scope Request)
  • Client は Resource Owner の同意を得たあと、Server に Reciprocal Authorization Code を送る
  • Server は取得したコードから Client に向けてAccess Token Request を送る
   # Access Token Response(AuthZ Code)
   HTTP/1.1 200 OK
   Content-Type: application/json;charset=UTF-8
   Cache-Control: no-store
   Pragma: no-cache

   {
     "access_token":"2YotnFZFEjr1zCsicMWpAA",
     "token_type":"example",
     "expires_in":3600,
     "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
     "reciprocal":"example_scope",
     "example_parameter":"example_value"
   }
   # AuthZ Response(Implicit)
   HTTP/1.1 302 Found
   Location: http://example.com/cb#
       access_token=2YotnFZFEjr1zCsicMWpAA&
       state=xyz&
       token_type=example&
       expires_in=3600&
       reciprocal="example_scope"   

図にするとこんな感じです。

Reciprocal OAuth.png

最後の Party B が Party A から Access Token を貰うところの認証のあたりとか、まだちょっと怪しいところがあるので今後整理されると思います。

OAuth 2.0 Security Best Current Practice

これはRFC6749/6750 に対して RFC6819 で脅威と対策が整理された後の様々な変化に対して更新された OAuth 2.0 のセキュリティベストプラクティスです。

  • RFC6819 に書かれてる内容についても何を推奨するかを整理
  • ブラウザのフラグメント処理とか、変わってるものに追従
  • Open Banking, eHealth, eGovernment ... より安全性が求められる環境も考慮
  • Dynamic Registration のユースケースも考慮

といった観点から、攻撃のモデルと対策として推奨される実装が整理されています。

「Implicit Grant と Resource Onwer Password Credential Grantには気をつけてください。」

この中で、Implicit Grant と Resource Onwer Password Credential Grant にも言及されています。

   In order to avoid these issues, clients SHOULD NOT use the implicit
   grant (response type "token") or any other response type issuing
   access tokens in the authorization response, such as "token id_token"
   and "code token id_token", unless the issued access tokens are
   sender-constrained and access token injection in the authorization
   response is prevented.

"Bearer Token" に対する "Sender-Constrained Token" がここでも出てきています。

ROPCについては "MUST NOT be used" です。

   The resource owner password credentials grant MUST NOT be used.

...

   Furthermore, adapting the resource owner password credentials grant
   to two-factor authentication, authentication with cryptographic
   credentials, and authentication processes that require multiple steps
   can be hard or impossible (WebCrypto, WebAuthn).

他にも色々あるので、読むのは大変ですが実践的な内容になっていると思います。

「整理の仕方がうまいのか、6819よりは辛くないかも?」

OAuth 2.0 for Browser-Based Apps

この仕様は Browser-Based Apps の OAuth 2.0 実装についてのベストプラクティスが整理されています。

OAuth 2.0 において、当初から Browser-Based Apps はいわゆる Public Client として Native Apps と同じような扱いになっています。
Native Apps については RFC8252 としてベストプラクティスが用意され、それをサポートしたいわゆる AppAuth と呼ばれるライブラリ群も用意されています。

Native Apps と Browser-based Apps の実装の類似点に注目し、ブラウザ上で実行されることで追加で考慮しなければならない点を含みます。

  • 基本は AuthZ Code Grant + PKCE 利用
  • バックエンドサーバーの有無によって推奨される実装を整理
  • AuthZ Code Grant への追加要件
  • Refresh Token の有効期限について

SPAだと JWT や localStrage がどうこうで度々燃えそうになりますが、結局はできることとできないことを整理してリスク需要していくという落とし所を探すしかありません。
上記 Security Best Current Practice への参照も結構あるようなので、合わせて読むのが良いでしょう。

最後に

日頃、OAuthやOpenID Connect(OIDC)あたりでQiitaの記事を検索して大きな誤りがないかを確認していますが、RFC6749/6750 + 7636ぐらいから先に踏み込まれた記事はあまり多くありません。
また、一部の詳しすぎる人が初学者向けの投稿に関連するRFC一覧などを載せたところで、一体どれだけの読者がそこから先に進んでいるのでしょうか?と思うところもあります。

これだけ読んでももちろん全ての仕様を完全に理解することは困難です。
しかし、既存のユースケースや設計/実装の課題に対し、どのようなアプローチで解決するための仕様があるかを覚えておけば、自分の設計や実装の場で壁にぶち当たった時にちょっとだけ役立つかもしれません。

ではまた。

(追記:これ書いてる時の様子)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away