1. はじめに
最近業務の中でOAuth2.0及びOpenID Connectの仕組みについて理解を深める機会があったので、そこで学んだ内容を自分なりにまとめてみました。(まだ完璧な理解には程遠いですが...)補足、不備等あればご指摘いただけると幸いです。
2. OAuth2.0とOIDC
OAuth2.0 = 認可(Authorization)のためのプロトコル
OIDC(OpenID Connect) = OAuth2.0の上に「認証(Authentication)」を乗せたプロトコル
OAuth2.0でなにを「認可」するのかというと、サードパーティアプリ(例.美容室予約アプリ)からリソースサーバ(例.Google Calendar API)へのアクセスを認可します。
その結果、例えば美容室予約アプリからGoogleカレンダーに予定を追加といった操作ができるようになり、より便利にアプリを利用することができます。
ただOAuth2.0は「認可」の仕組みなので、「認証」は保証してくれません。OIDCはそこを補います。
3. 登場人物
| 名前 | 役割 | 具体例 |
|---|---|---|
| リソースオーナー | データの持ち主(ユーザ) | Googleアカウントのユーザー |
| クライアント | アクセスしたいアプリ | 美容室予約アプリ |
| 認可サーバー | 権限を発行するサーバー | Googleの認証基盤 |
| リソースサーバー | 実際のデータを持つサーバー | Google Calendar API |
4. 認可コードフロー
4-1 基本的な流れ
なんやらごちゃごちゃしてますが、要はOAuth2.0の目的はリソースサーバへのアクセストークンを取得することです。そのために以下2つの手順を実施します。
- 認可リクエスト:認可サーバに認可リクエストを飛ばします。このレスポンスとして認可コードを取得します。
- トークンリクエスト:認可リクエストで取得した認可コードをもとにトークンリクエストを行い、アクセストークンを取得します。
※各リクエストの具体的なリクエストパラメータについては7節でまとめていますが、認可リクエスト時のscopeに"openid"を含めることで認可サーバーにOIDCリクエストとして解釈され、アクセストークンに加えてIDトークンを取得することができます。
4-2 アクセストークンとIDトークン
アクセストークンはAPIへのアクセス用の「鍵」、IDトークンはユーザーが誰かを証明する「身分証明書」といったイメージです。OIDCでは認可サーバーが IDトークン(JWT形式) を発行します。
※JWTの構造
JWTは.で区切られた3つのパーツで構成されています。
① ヘッダー(Header) : 署名アルゴリズムとトークンの種類を示します。
② ペイロード(Payload) : ユーザー情報やトークンのメタデータ(クレーム)が含まれます。
③ 署名(Signature) : ヘッダーとペイロードを認可サーバーの秘密鍵で署名したものです。
※ペイロードの構造例
| フィールド | 意味 |
|---|---|
iss |
トークンを発行したサーバー(Issuer) |
sub |
ユーザーの一意なID(Subject) |
aud |
このトークンの受け取り手(自アプリのクライアントID) |
iat |
発行日時 |
exp |
有効期限 |
5. トークン取得後の認証認可の流れ
5-1 ログイン時(OIDCによる認証)
IDトークンを取得したあと、アプリ側では以下の流れでログイン処理を行います。
- IDトークンを取得する — トークンエンドポイントのレスポンスから取得
- 署名を検証する — 認可サーバーの公開鍵(JWKSエンドポイント)で検証
-
クレームを検証する —
iss/aud/exp/nonceが正しいか確認 -
ユーザーを特定する —
subでDBを検索し、存在しなければ新規登録 - セッションを発行する — ログイン完了
5-2 API呼び出し時(認証 + 認可)
ログイン後にAPIを呼び出す際は、認証と認可の2段階で確認が行われます。
-
セッションチェック(認証) — 「誰からのリクエストか」を確認。無効なら
401 Unauthorized -
アクセストークンチェック(認可) — 「このAPIを呼ぶ権限があるか」を確認。権限なしなら
403 Forbidden
6. セキュリティパラメータを理解する
| パラメータ | 検証者 | 守っているもの | 防いでいる攻撃 | 他のパラメータとの違い |
|---|---|---|---|---|
state |
クライアント | ユーザーのセッション | CSRF攻撃(他人のコードを押し付けられる) |
PKCE が「コードの盗難」を防いでも、state がないと「自分のコードを他人に踏ませる」攻撃が防げない |
PKCE |
認可サーバー | 認可コード | 横取り攻撃(コードを盗んでトークンに交換される) |
state が「正しいセッション」を保証しても、コード自体が盗まれたら PKCE がないとトークンを奪われる |
nonce |
クライアント | 身分証明書の鮮度 | リプレイ攻撃(過去のトークンを使い回される) |
state や PKCE は「手続き」を守るが、nonce は「発行された後のトークン」の再利用を直接防ぐ |
at_hash |
クライアント | アクセストークンの正当性 | すり替え攻撃(別フローのトークンを混入される) | 他の3つは「正しいフローかどうか」を守るが、at_hash は「セットで発行されたトークンかどうか」を守る |
※1 CSRF攻撃において自分の認可コードを他人に踏ませると攻撃者にとってなにが嬉しいのか
CSRF攻撃について具体的な問題が理解しずらかったので、攻撃者側のメリットをまとめました。
📌パターン1(攻撃者が仕込んだ認可コードに対応するスコープがGoogleIDの連携機能だった場合)
アプリ側は「被害者が自分のアカウントに、このGoogle IDを紐付けようとしている」と誤認し、被害者のデータと攻撃者のGoogle IDを連結してしまいます。(※このGoogleIDはIDトークンのsubクレームとして含まれます。)
その結果攻撃者は自分のgoogleアカウント情報で、被害者のアカウントとしてログインができてしまいます。(これえぐい...)
📌パターン2(攻撃者が仕込んだ認可コードに対応するスコープがリソースサーバへのアクセス権限だった場合)
このとき攻撃者は被害者のリソースにアクセスすることはできませんが、逆に被害者が攻撃者のリソースにアクセスしてしまいます。その事実に気づかず被害者がアカウント情報等を入力すると、その情報が攻撃者に筒抜けになってしまいます。(これもえぐい...)
※2 PKCEの防御対象について
認可コードの横取りを防ぐと書きましたが、セッションIDをキーにcode_verifierを管理している構成ならPKCEが実質的にCSRFも防げます。
7. リクエストパラメータまとめ
これまでの内容を踏まえ、認可コードフローで実際に送るリクエストのパラメータを一覧にまとめてみました。
7-1 認可リクエスト(Authorization Request)
認可サーバーの認可エンドポイントへGETリクエストで送るパラメータです。
| パラメータ | 必須/任意 | 説明 |
|---|---|---|
response_type |
必須 | レスポンスの種類。認可コードフローでは code を指定 |
client_id |
必須 | クライアントアプリの識別子。認可サーバーへの事前登録時に発行される |
redirect_uri |
推奨 | 認可後にリダイレクトされるURL。事前登録したURIと一致している必要がある |
scope |
推奨 | 要求するアクセス権限の範囲(例: openid email profile)。openid を含めるとOIDCリクエストになる |
state |
推奨 | CSRF攻撃防止用のランダムな文字列。コールバック時にそのまま返却され、クライアントが検証する |
code_challenge |
PKCE使用時 |
code_verifier をSHA-256でハッシュしBase64URLエンコードした値 |
code_challenge_method |
PKCE使用時 | ハッシュアルゴリズムの指定。通常 S256
|
nonce |
OIDCで推奨 | リプレイ攻撃防止用のランダムな文字列。IDトークンのクレームに埋め込まれる |
7-2 トークンリクエスト(Token Request)
認可サーバーのトークンエンドポイントへPOSTリクエストで送るパラメータです。
| パラメータ | 必須/任意 | 説明 |
|---|---|---|
grant_type |
必須 | 認可の種類。認可コードフローでは authorization_code を指定 |
code |
必須 | 認可リクエストのレスポンスとして取得した認可コード |
redirect_uri |
条件付き必須 | 認可リクエストに redirect_uri を含めた場合は必須。同じ値を指定する |
client_id |
条件付き必須 | 公開クライアント(モバイル・SPAなど)の場合は必須 |
client_secret |
機密クライアントのみ | サーバーサイドアプリなど、秘密を安全に保管できるクライアントのみ使用 |
code_verifier |
PKCE使用時 | 認可リクエスト時に生成した元の乱数。認可サーバーが code_challenge と照合する |
※トークンリクエスト時はリダイレクトされませんが、認可リクエストと同じ値かどうかをチェックするために設定するようです。
