OAuth 2.0 / OpenID Connect の Hybrid Flow への向き合い方

ritouです。

f:id:ritou:20200312114947p:plain

OAuth 2.0 / OIDC を触って「そろそろ完全に理解したって言っちゃおうかな」なんて思った時に出会ってしまうのが Hybrid Flow です。 某書籍のレビュー時に Hybrod Flow について著者といくつかやりとりをしたのですが、なんだかんだで結構ややこしいので私の考える向き合い方を書き残しておきます。

Hybrid Flow とは

Authorization Code Flow(Grant) や Implicit Grant(Flow) に比べて、まず定義からよくわからないと言う声を多く聞きます。 仕様を紹介している記事ではこんな感じで書かれています。

Hybrid Flow Authorization Code といくつかのトークンが Authorization Endpoint から返され, その他のトークンが Token Endpoint から返される OAuth 2.0 のフロー.

f:id:ritou:20200311135326p:plain

Final: OpenID Connect Core 1.0 incorporating errata set 1

OpenID Connect の「ハイブリッドフロー」を用いることにより、単一のクライアントに対して 2 つのアクセス・トークンを発行することが可能です。想定されるクライアントとしては、モバイル端末側のネイティブアプリケーションとWebサーバー側のバックエンドアプリケーションからなる構成が挙げられます。このようなクライアントに関して、ネイティブアプリケーションに発行するほうのアクセス・トークンについてはスコープを制限し(リクエストされているスコープのサブセットとし)、パブリッククライアント特有のセキュリティリスクを最小化することが、しばしば求められます。

ハイブリッドフロー: スコープを制限したアクセストークンの発行 — Authlete ナレッジベース

このフローはAuthorizationエンドポイントとTokenエンドポイント双方からAccess Tokenが発行されることになることから、Hybrid(ハイブリッド)フローと呼ばれる。なおHybridフローはRFC 6749では定義されておらず、OpenID Connectの策定段階においてOAuth 2.0の拡張仕様として出てきた「OAuth 2.0 Multiple Response Type Encoding Practices」(英語)というドキュメントに定義されている。

www.buildinsider.net

ネイティブアプリの場合、OAuth 2.0ではImplicitフロー(response_type=token)を使うケースとHybridフロー(response_type=code token)を使う場合があった。

OpenID Connectでもネイティブアプリ側で受け取ったID TokenをBackend Serverに送ることは不可能ではないが、そうするとID Tokenがブラウザーの前のユーザー(正規のユーザーとは限らない)によって改ざんされる恐れがあるため、ID Tokenの署名検証が必須になり、Implicitフロー(response_type=token id_token)を使うとHybridフロー(response_type=code token)を利用する場合と比べて複雑になる。

www.buildinsider.net

全パターンの解説はこちらに詳しく書かれています。

qiita.com

と言うことで、仕様としては

  • OAuth 2.0 の AuthZ Request で response_type=code token とすると AuthZ Response に "response_type=code" と "response_type=token" の場合のレスポンスが合わさったものが返される。
  • OIDC ではそれに ID Token が絡む。仕様では code id_token,code token, code id_token token に言及。

と言う感じで、

  • AuthZ Code 以外を含むので flagment に指定される
  • AuthZ Code を Token Endpoint に送って Token を取得する
  • AuthZ Response に含まれる Token と Token Response に含まれる Token は内容(Access Token の scope や expiration, ID Token の payload の中身)が必ずしも一致しない

と言うあたりでしょう。

そして、これを適用するユースケースとして

  • ネイティブアプリや SPA が AuthZ Response に含まれた Token を利用してリソースアクセス
  • ネイティブアプリやSPAのバックエンドサーバーが AuthZ Code を使って Token Request を送り、Token Response に含まれた Token を利用してリソースアクセス

と言うのが例として上げられます。

(図描くのがめんどくさい!!!)

効果的な使い方

例えば Google は response_type の組み合わせに対応していますが、それだけで「じゃあ使おうか」となるかと言うと別でしょう。 次のように、提供するリソースアクセスに幅がある IdP の場合に、効果が発揮できると思います。

  • SNSへの投稿や広告/ターゲティング系など : IdP が単体で Public Client からの利用を許可していたり、ネイティブアプリや SPA からの利用を想定している機能
  • 決済のための機能 : IdP が Confidential Client のみに利用を許可している機能

この辺りが想像しにくい部分かなと思っています。

もちろん、Authorization Code Grant を利用してバックエンドサーバーに Access Token を保存しておき、全てのリソースアクセスをバックエンドサーバーから行う設計の方が安全と言えるでしょう。 しかし、負荷などの面で Public Client からの利用が許可されているものはネイティブアプリ/SPAからしちゃうような設計の場合、Hybrid Flow の選択肢が出てくるかもしれません。

AuthZ Response / Token Response に含まれる各種トークンの違い

最初に、AuthZ Response に含まれる Token と Token Response に含まれる Token は内容(Access Token の scope や expiration, ID Token の payload の中身)が必ずしも一致しないと紹介しました。この辺りに言及された記事がありました。

Access Tokenが違った値が返されることがあります。これは例えばAuthorization Codeと引き換えに得たAccess Tokenはセキュリティ的により強固なフローを経ているのでより長い有効期限のTokenにしよう、などというようにサーバー側で設定されている場合にありえます。

ID Tokenについても、認証リクエストのID Tokenで得られる属性情報はいくつかのClaimが抜けていて、完全な形の属性情報を得られるトークンをToken Requestで返す、というようなサーバー側の実装があり得る(プライバシー上の理由などから)ので、Token Requestでより完全なトークンを得られるというメリットがあります。

qiita.com

有効期限の話で言うと、逆にセンシティブなscopeが付与されたAccess Tokenの有効期限が短くなるような場合もありえるでしょう。 このようなIdPの場合、複数の scope を Access Token に紐づけると有効期限が短い方に引っ張られてしまう可能性があり、全てをバックエンドサーバーで行う場合にRefresh Token を用いた Access Token の更新作業が多く発生する事になるでしょう。 Hybrid Flow を利用することで、ネイティブアプリや SPA では有効期限が長い scope が付与された Access Token を扱い、バックエンドサーバーでは短い有効期限である Access Token を扱ったり、極端な場合は使うたびに Refresh Token から取得し直すと言う設計もありえるかもしれません。

このような理想形を実現するためには IdP 側も scope 管理をよく考慮して実装されている必要がありますし、Client 側もそれを理解した上で設計を行う必要があります。

各種攻撃、対策の考え方

書籍のレビューをした際、ネイティブアプリ、SPAとバックエンドサーバーの組み合わせに対して起こりうる攻撃とその対策を考えるときに、

  • ネイティブアプリ/SPA : Public Client としての攻撃を想定し、対策を行う
  • バックエンドサーバー : Confidential Client として攻撃を想定し、対策を行う

として組み合わせるべきだという話をして、実際にその考えを採用していただきました。 たまに「この攻撃、ネイティブ側で対応したら問題ないのでは?」と言う考えを見かけますが、避けるべきでしょう。

また、設計としてネイティブアプリ/SPA とバックエンドサーバー間でアクセストークンのやりとりも避けるべきでしょう。

  • バックエンドサーバーにAuthorization Code を送る : バックエンドサーバーが Client 認証を行い Token Request を送れる、PKCE や ID Token を利用した検証も可能 なので使うべき
  • バックエンドサーバーにID Token を送って認証 : nonceなど Payload の値を検証が可能。Hybrid Flow の話とは変わるが、バックエンドサーバーで認証だけを行う場合は選択肢としてありそう
  • バックエンドサーバーに Access Token を送ってリソースアクセス : Access Token の厳密な検証など、置き換えなどへの対策が困難 使ってはいけない。
  • バックエンドサーバーからネイティブアプリ/SPAに Access Token を渡す : サーバー間通信でしかやりとりされず、ClientSecret と同様に安全に管理されることを前提としている Confidential Client 向けに発行された Access Token が利用者の端末を経由してリソースサーバーに送られるため、漏洩時のリスクが大きい場合がある。これも使うべきではない。

まとめ

  • Hybrid Flow とはどんなものか
  • 効果的な使い方、それに必要な IdP / Client の設計
  • 各種攻撃対策の考え方、やりとりされるトークンの扱いについて

と言うあたりを書きました。 例の書籍を読む際の参考になればと思います。

ではまた。