OAuth & OIDC の認可コードフロー
OAuth & OIDC の認可コードフローについて説明します。
特に重要なのが、confidential client, public client という概念です。confidential client は、シークレットを保持できるクライアントであり、public client はシークレットを保持できないクライアントです。
OAuth + confidential client
- 背景
- 日記アプリのバックエンドが、ユーザの Google カレンダー情報にアクセスしたい。
- 登場人物
- Authorization Server: Google 保有。Google カレンダーにアクセスするためのアクセストークンを発行する。
- Resource server: Google 保有。Google カレンダーの情報を持っている。
- Resource Owner: ユーザ
- Client: 日記アプリのバックエンド
- 詳細
- 最終的に、アクセストークンは、日記アプリのバックエンドに配置される。
- 日記アプリのバックエンドは、シークレットを保持することができるので、confidential client となります。
- フローの最後に、日記アプリのバックエンドは、リダイレクトによって受け取った code と元々保持していた client secret を引っ提げて、Authorization Server にアクセストークンをもらいに行きます。client secret は攻撃者に知られていないはずなので、Authorization Server は client secret によって本物の日記アプリのバックエンドであることを検証できます。client secret の検証がないと、攻撃者に code を奪われたら、アクセストークンが取得されてしまいます。
- OAuth は認可です。
OAuth + public client ※ PKCE を使います
- 背景
- 日記モバイルアプリのフロントエンドが、ユーザの Google カレンダー情報にアクセスしたい。
- 日記モバイルアプリは、バックエンドを持たないものとする。
- 登場人物
- Authorization Server: Google 保有。Google カレンダーにアクセスするためのアクセストークンを発行する。
- Resource server: Google 保有。Google カレンダーの情報を持っている。
- Resource Owner: ユーザ
- Client: 日記アプリのフロントエンド
- 詳細
- 最終的に、アクセストークンは、日記モバイルアプリのフロントエンドに配置される。
- 日記モバイルアプリのフロントエンドは、シークレットを保持できないので、public client となります。
- フローの最後に、日記モバイルアプリのフロントエンドは、リダイレクトによって受け取った code とフロー開始時に生成した code_verifier を引っ提げて、Authorization Server にアクセストークンをもらいに行きます。Authorization Server は code_verifier によって本物の日記モバイルアプリのフロントエンドであることを検証できます。code_verifier と code_challenge(code_verifier をハッシュ化したもの)は、認可フローの開始時に生成され、Client は手元に code_verifier を保持しておき、code_challenge は最初のリクエストに添えます。最後に、code とともに code_verifier を送ることで、Authorization Server は code_verifier と code_challenge と突き合わせて、本物の日記モバイルアプリのフロントエンドであることを検証できます。
- code_verifier と code_challenge を使った仕組みが PKCE であり、一昔前は PKCE の仕組みがなく code のみの検証だったのですが、攻撃者に code を盗まれてアクセストークンを取得されてしまう事例が多発したため、PKCE が導入されました。
- OAuth は認可です。
OIDC + public client ※ PKCE を使います
- 背景
- 日記アプリのフロントエンドが、日記アプリのバックエンドの認証を突破したい。
- 認証に Google Login を使いたい。
- 登場人物
- Identity Provider: Google 保有。日記アプリのバックエンドの認証に使える ID トークンを発行する。
- Relying Party: 日記アプリのフロントエンド
- 詳細
- 最終的に、IDトークンは、日記アプリのフロントエンドに配置される。
- このフローは、日記アプリのバックエンドが Google を全面的に信頼していることで成り立ちます。
- 認証フローの開始時に、scope=openid email profile をクエリパラメータに含めることで、Identity Provider(Google)に対してIDトークンにユーザーの基本情報、メールアドレス、プロフィール情報を含めることを要求します。
- フローの最後に、日記アプリのフロントエンドは、リダイレクトによって受け取った code とフロー開始時に生成した code_verifier を引っ提げて、Identity Provider にIDトークンをもらいに行きます。これも PKCE の仕組みを使っています。
- IDトークンの目的は、Google のリソースにアクセスすることではなく、日記アプリのバックエンドの認証を突破するためです。日記アプリのバックエンドは Google を信頼しているため、Google が発行したIDトークンを認証に使えます。
- OIDC は認証です。
実装例: Cognito + Google ログインの詳細フロー
以下は、AWS Cognito を使った Google ログインの実際の実装フローです。
-
ユーザーが Cognito Hosted UI にアクセス
https://xxx.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize? response_type=code&client_id=xxx&redirect_uri=http://localhost:8090/callback& scope=openid%20profile%20email&state=xxx&code_challenge_method=S256& code_challenge=xxx&identity_provider=Google
- Cognito Hosted UI は認証画面(メールアドレス&パスワード入力欄、Google ボタン、Facebook ボタン)
-
code_challenge
パラメータにより PKCE フローが開始 - デスクトップアプリは
localhost:8090
でサーバを起動して待機 -
identity_provider=Google
が指定されているため、Cognito Hosted UI の認証画面が一瞬表示された後、即座に Google アカウント選択画面へリダイレクト - Cognito は
code_challenge
を保管
-
Cognito から Google へリダイレクト
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount? client_id=xxx&redirect_uri=https://xxx.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse& scope=openid%20email%20profile&response_type=code&state=xxx
-
Google でのユーザー認証
- ユーザーが Google アカウントを選択
- 初回ログイン時は同意画面が表示される
-
Google から Cognito へ認可コード送信
- Google がユーザー認証を完了し、認可コード(
code
)を発行 - Cognito の
/oauth2/idpresponse
エンドポイントにリダイレクト - この
code
により、Cognito は Google のユーザー情報にアクセス可能
- Google がユーザー認証を完了し、認可コード(
-
Cognito でのユーザー検証
- Cognito が Google から受け取った
code
を使って Google のトークンエンドポイントにアクセス - Google が Cognito に ID トークンを返却(ID トークン内に
scope=openid email profile
で要求されたユーザー情報が含まれる) - Cognito はユーザーが正当な Google アカウントを持つことを確認
- Cognito が Google から受け取った
-
Cognito からデスクトップアプリへリダイレクト
- Cognito が新しい認可コード(Google のものとは別)を発行
- デスクトップアプリのコールバック URL にリダイレクト
http://localhost:8090/callback?code=xxx&state=xxx
-
デスクトップアプリでの認可コード受信
-
localhost:8090
で待機していたデスクトップアプリが認可コードを受信
-
-
トークン取得
- デスクトップアプリが認可コード +
code_verifier
を Cognito に直接 POST リクエスト
POST https://xxx.auth.ap-northeast-1.amazoncognito.com/oauth2/token Content-Type: application/x-www-form-urlencoded [リクエストボディ] grant_type=authorization_code&client_id=xxx&code=xxx& redirect_uri=http://localhost:8090/callback&code_verifier=xxx
-
redirect_uri
は認可リクエスト時と同じ値で検証用(リダイレクトのためではなく、OAuth 2.0 仕様による検証パラメータ) -
code_verifier
は PKCE による認証確認用
- デスクトップアプリが認可コード +
-
ID トークン・アクセストークンの発行
- Cognito が ID トークンとアクセストークンをデスクトップアプリに返却
- デスクトップアプリは認証完了
このフローでは、Cognito が Google と デスクトップアプリの仲介役として機能し、純粋な OIDC 認証(openid email profile
スコープ)を提供しています。
他のフロー
OAuth と言えば認可コードフローというイメージですが、一応他にもあります。