はじめに
前回の記事でOAuthについて紹介しました。
今回はOAuthに関するRFC 6749において定義されている認証のフローについて調査してまとめていきたいと思います。
OAuth2.0で定義されているフロー
認可コード
一時的に発行される認可コードとアクセストークンの交換フローインプリシット
認可エンドポイントからアクセストークンが直接発行されるフローリソースオーナー・パスワード・クレデンシャルズ
ユーザーのIDとパスワードをクライアントアプリに渡すフロークライアント・クレデンシャルズ
ユーザー認証なし。クライアントアプリの認証のみが必要なフロー
以上4つのフローについて調査していきます。
また、上記に加えてアクセストークン再発行時のフローについても記述します。
ここで登場する「クレデンシャル」というのは、IDやパスワードをはじめとしたユーザー等の認証に用いられる情報を指します。
認可コードフロー
RFC6749にて定義されているフローです。
大まかな流れとしては認可エンドポイントから一時的な認可コード
を受け取り、その認可コード
をトークンエンドポイントに投げてアクセストークンと交換します。
具体的な流れ
以下のように用語を使用します。
アプリ...ユーザーが利用するアプリ
サービス...認可のためのサービス
- アプリはサービスと連携するかどうかユーザーに対して確認する
- アプリはサービスの認可サーバーの
認可エンドポイント
に対して認可リクエストを送信する - サービスはアプリに認可画面を返す
- アプリはユーザーに認可画面を表示する
- ユーザーは認可画面にて、アプリが要求している権限を確認し、サービスで利用している自身のログインIDとパスワードを入力、アプリの認可リクエストを承認する
- サービスはアプリに一時的な認可コードを発行する
- アプリはサービスのトークンエンドポイントに認可コードを送信する
- サービスはアプリにアクセストークンを発行する
インプリシットフロー
RFC67494.2にて定義されているフローです。
認可エンドポイントに認可リクエストを投げ、直接アクセストークンを受け取るフローです。
認可コードフローとの違いは、「認可エンドポイントが直接アクセストークンを発行する」点です。
つまり、トークンエンドポイントを使用せずにアクセストークンを発行します。
具体的な流れ
- アプリはサービスと連携するかどうかユーザーに対して確認する
- アプリはサービスの認可サーバーの
認可エンドポイント
に対して認可リクエストを送信する - サービスはアプリに認可画面を返す
- アプリはユーザーに認可画面を表示する
- ユーザーは認可画面にて、アプリが要求している権限を確認し、サービスで利用している自身のログインIDとパスワードを入力、アプリの認可リクエストを承認する
- サービスはアプリにアクセストークンを発行する
認可コードフローとの違いは、6.
の手順の際に直接アプリにアクセストークンを発行し、認可コードを使用していません。
リソースオーナー・パスワード・クレデンシャルズフロー
RFC6749にて定義されているフローです。 トークンエンドポイントにトークンリクエストを投げ、応答としてアクセストークンを受け取るフローです。 クライアントアプリがユーザーIDとパスワードを受け取る点が特徴です。
具体的な流れ
- アプリはサービスと連携するかどうかユーザーに対して確認する
- アプリはユーザーにIDとパスワードを入力させるための画面を表示する
- アプリの画面にサービスにログインするためのIDとパスワードを入力する
- アプリは、入力されたIDとパスワードを含んだリクエストをサービスの認可サーバーのトークンエンドポイントに送信する。
- サービスはアプリにアクセストークンを発行する
インプリシットフローとの違いは、手順2.
にて表示される認可画面はサービスのものではなく、クライアントアプリのものであるという点です。
ユーザーIDとパスワードはクライアントアプリケーションが受け取ります。
クライアント・クレデンシャルズフロー
RFC6749にて定義されているフローです。
トークンエンドポイントにトークンリクエストを投げ、アクセストークンを受け取ります。
ユーザーの認証を行うことなく、クライアントアプリケーションの認証のみがおこなわれます。
具体的な流れ
- アプリはサービスの認可サーバーのトークンエンドポイントにトークンリクエストを送信する(アプリのクライアントIDとクライアント・シークレットを併せて送信する)
- サービスはアプリにアクセストークンを発行する。
リフレッシュトークンフロー
RFC6749にて定義されているフローです。
事前に発行されていたリフレッシュトークンをトークンエンドポイントに提示することにより、アクセストークンの再発行を受けます。
- 過去の認可リクエストの結果、アクセストークンと共にリフレッシュトークンの発行を受けていることが前提
- アプリは、サービスの認可サーバーのトークンエンドポイントにアクセストークンの再発行を依頼します。
- サービスはアプリにアクセストークンを発行する。
フローのまとめ
ここまで、RFC6749に定義されている仕様についてまとめてみました。
認可エンドポイント及びトークンエンドポイントについては使用するかどうかはフローによって異なります。
- 認可コード
認可エンドポイントとトークンエンドポイントの両方を使用する - インプリシット
認可エンドポイントのみ使用する - リソースオーナーパスワードクレデンシャルズ
トークンエンドポイントのみ使用する - クライアントクレデンシャルズ
トークンエンドポイントのみ使用する - リフレッシュトークン
トークンエンドポイントのみ使用する
認可コード横取り攻撃について
スマートフォンにおけるOAuth利用の際に認可コード横取り攻撃
と呼ばれる攻撃が存在するらしいので調査します。
前提 - カスタムURLスキームの乗っ取り
カスタムURIスキームはモバイルアプリ内のコンテンツへ直接誘導するディープリンクに広く利用されています。
が、カスタムURLスキームを偽装した不正アプリは正規アプリへのディープリンクを乗っ取る事ができます。
対策としては下記RFCにて定義されているPKCE
というものが使用されます。
RFC7636
PKCE(Proof Key for Code Exchange by OAuth Public Clients)
はOAuth2.0の拡張機能で、導入することで認可コード横取り攻撃を無効化できます。
PKCE
を使用しないOAuth2.0の仕様では、認可サーバーはネイティブアプリをクライアント認証することができないため、認可コードを横取りした不正アプリと正規のアプリを識別することができません。
ネイティブアプリにおけるクライアント認証について
ネイティブアプリのOAuthフローには認可コードフローが推奨されています。
ただし、ネイティブアプリのOAuthフローでは、認可サーバーがクライアント認証することができません。
OAuth2.0の仕様では、パスワードなどのクレデンシャルによるクライアント認証を行うフローがありますが、ネイティブアプリの識別を目的とするクライアント認証を禁止しています。
その理由は、ネイティブアプリに組み込まれたクレデンシャルはリバースエンジニアリングや通信解析により機密性を維持できないためです。
クライアント認証できない場合、認可サーバーはリダイレクトURIに基づいてネイティブアプリを識別します。
OAuth2.0の仕様におけるセキュリティ上の検討事項では、クライアント認証が不可能な場合、認可サーバーはクライアントを識別するための他の手段を採用するようにもとめています。
具体的な手段としては、認可サーバーはクライアントへのリダイレクトURIを事前に登録しておき、認可要求に含まれるリダイレクトURIと比較する等です。
これによって認可サーバーはリダイレクトURIが一致しないアプリを不正と判断し、認可コードを付与しないということが可能になります。
しかし、ここでリダイレクトURIにカスタムURLスキームが使用された場合に、認可サーバーはネイティブアプリを識別できません。
OAuth2.0の仕様上はネイティブアプリがリダイレクトURIにカスタムURLスキームを使用することを認めていますが、AndroidとiOSはカスタムURLスキームに基づいてアプリを識別できません。
そのため、認可サーバーが正規アプリへのリダイレクトURIの一致を確認していても、OSの処理によって認可コード付きのURLが不正アプリへリダイレクトされてしまいます。
不正アプリは、認可コードを横取りし、正規アプリになりすましてアクセストークンを発行することができます。
これを認可コード横取り攻撃
といいます。
PKCEによる認可コード横取り攻撃の検知
PKCEを導入したOAuthのフローでは、認可要求とアクセストークン要求を送信したネイティブアプリが同じであるかを認可サーバーが判断できるようになります。
- 正規アプリは認可要求の際に一時的な鍵を生成し、その鍵を変換したチャレンジを認可サーバーに送信します。
- アクセストークン要求の際に鍵を認可サーバーに送信します。人ァサーバーは受信した鍵彼チャレンジを計算し、認可要求の際に一時的な鍵を生成し、認可要求の際に受信したチャレンジと比較します。
- チャレンジが一致すれば同じアプリからの要求であることが証明されるため、認可コードを付与した正規アプリにのみアクセストークンを発行できるようになります。
ネイティブアプリにおけるOAuth2.0のベストプラクティスはRFC8252に定義されており、ネイティブアプリのOAuthフローへのPKCEの導入を必須としています。
ただ、PKCEを導入したとしてもカスタムURLスキームを乗っ取った不正アプリが偽のログインを要求するなどの攻撃への懸念は残ります。
そのため、RFC8252や現状のOAuth2.1では、リダイレクトURIにはカスタムURLスキームではなく、ユニバーサルリンクのような乗っ取りが困難なHTTP URLスキームの仕様を推奨しています。
カスタムURLスキームとは
ネイティブアプリにおける他のアプリとの連携を行うための手段です。
「hoge://fuga」のような形式のhogeの部分をURLスキームといい、これをアプリ独自に定義し、そのURLを別のアプリから開いた際、スキームを定義下アプリの起動とデータの送信を行うための機構です。
ユニバーサルリンクとは
Appleのディープリンク技術です。
ディープリンクとは、ユーザーを直接アプリに誘導するリンクのことです。
ユニバーサルリンクを実装することで、検索ユーザーがiOSで情報を検索し、表示されたWebサイトにアクセスした際に、URLに対応するスマホアプリの特定のコンテンツへユーザーを遷移させます。
参考
OAuth 2.0 全フローの図解と動画
OAuth 2.0 + OpenID Connect のフルスクラッチ実装者が知見を語る
OAuth & OpenID Connect 関連仕様まとめ
PKCE: 認可コード横取り攻撃対策のために OAuth サーバーとクライアントが実装すべきこと
OAuthにおける認可コード横取り攻撃とその対策
カスタムURLスキームの乗っ取りとその対策
ネイティブアプリで OAuth 2.0 を安全に使うための OAuth 拡張
所感
今回はOAuth2.0の4つのフローとその具体的な内容について調査しました。
また、関連する仕様としてネイティブアプリの場合の認可コード横取り攻撃
についてとその対処についても調査しました。
正直内容が難しい部分が多く、まとめるのに時間がかかってしまいましたがフローの内容や認可の仕組みについて理解が深まったと思います。
内容を完全に理解できた自信は無いですが、大まかに人に説明できるレベルにはなったかなと思います。
今後も認証、認可については記事にしていきたいと思います。