OAuth 2.0 入門 - 認可フレームワークの仕組みとフロー
OAuth 2.0は、サードパーティアプリケーションがユーザーのリソースに対して限定的なアクセス権を取得するための認可フレームワーク。RFC 6749で定義されている。
認可と認証の違い
OAuth 2.0を理解する上で、認可(Authorization) と 認証(Authentication) の区別は重要。
| 概念 | 目的 | 答える問い | OAuth 2.0での扱い |
|---|---|---|---|
| 認証(Authentication) | 「誰であるか」を確認する | Are you who you claim to be? | OAuth 2.0自体は認証の仕組みを持たない |
| 認可(Authorization) | 「何ができるか」を許可する | What are you allowed to do? | OAuth 2.0の主要な目的 |
OAuth 2.0は認可のためのフレームワークであり、認証プロトコルではない。認証が必要な場合はOAuth 2.0の上に構築されたOpenID Connect(OIDC)を使用する。
登場人物(ロール)
OAuth 2.0には4つの主要なロールが定義されている。
| ロール | 説明 | 具体例 |
|---|---|---|
| リソースオーナー(Resource Owner) | 保護されたリソースへのアクセスを許可できるエンティティ。通常はエンドユーザー | Googleアカウントの所有者 |
| クライアント(Client) | リソースオーナーの代理としてリソースにアクセスするアプリケーション | サードパーティのWebアプリ、モバイルアプリ |
| 認可サーバー(Authorization Server) | リソースオーナーを認証し、アクセストークンを発行するサーバー | Google OAuth、Auth0、Keycloak |
| リソースサーバー(Resource Server) | 保護されたリソースをホストし、アクセストークンを検証してリクエストに応答するサーバー | Google Calendar API、GitHub API |
認可サーバーとリソースサーバーは同一のサーバーである場合もある。
クライアントの種類
OAuth 2.0ではクライアントを機密性の観点で2つに分類する。
| 種類 | 説明 | 例 |
|---|---|---|
| コンフィデンシャルクライアント | クライアントシークレットを安全に保持できる | サーバーサイドWebアプリ |
| パブリッククライアント | クライアントシークレットを安全に保持できない | SPA、モバイルアプリ、デスクトップアプリ |
パブリッククライアントではクライアントシークレットに依存したセキュリティは確保できないため、PKCEなどの追加対策が必要になる。
認可グラント種別
認可グラント(Authorization Grant)は、クライアントがアクセストークンを取得するための方法。用途に応じて複数の種別が定義されている。
| グラント種別 | 用途 | クライアント種別 | RFC |
|---|---|---|---|
| 認可コード(Authorization Code) | サーバーサイドWebアプリ | コンフィデンシャル | RFC 6749 |
| 認可コード + PKCE | SPA、モバイルアプリ | パブリック | RFC 7636 |
| クライアントクレデンシャル(Client Credentials) | マシン間通信(M2M) | コンフィデンシャル | RFC 6749 |
| デバイスコード(Device Authorization) | 入力制限のあるデバイス(TV、IoT等) | パブリック | RFC 8628 |
注意: インプリシットグラントとリソースオーナーパスワードクレデンシャル(ROPC)は、セキュリティ上の問題からOAuth 2.1ドラフトで廃止予定。新規実装では使用しないこと。インプリシットグラントはトークンがURLフラグメントに露出するリスクがあり、代わりに認可コード + PKCEを使用する。
認可コードフロー
最も一般的なOAuth 2.0フロー。サーバーサイドWebアプリケーション向け。
sequenceDiagram
participant User as リソースオーナー<br/>(ブラウザ)
participant Client as クライアント<br/>(Webアプリ)
participant AuthServer as 認可サーバー
participant Resource as リソースサーバー
User->>Client: 1. サービスにアクセス
Client->>User: 2. 認可サーバーへリダイレクト
User->>AuthServer: 3. 認可リクエスト<br/>(client_id, redirect_uri, scope, state)
AuthServer->>User: 4. ログイン画面表示
User->>AuthServer: 5. 認証 + 同意
AuthServer->>User: 6. 認可コードとともにリダイレクト
User->>Client: 7. 認可コード + state
Client->>AuthServer: 8. トークンリクエスト<br/>(認可コード + client_secret)
AuthServer->>Client: 9. アクセストークン + リフレッシュトークン
Client->>Resource: 10. APIリクエスト<br/>(Authorization: Bearer トークン)
Resource->>Client: 11. リソース応答
認可リクエストのパラメータ
GET /authorize?
response_type=code
&client_id=CLIENT_ID
&redirect_uri=https://example.com/callback
&scope=read:user
&state=xyz123
| パラメータ | 必須 | 説明 |
|---|---|---|
response_type | ○ | code を指定 |
client_id | ○ | 事前に認可サーバーに登録したクライアント識別子 |
redirect_uri | △ | 認可後のリダイレクト先(事前登録と一致が必要) |
scope | △ | 要求するアクセス範囲 |
state | ○ | CSRF対策のためのランダム文字列。レスポンスで返却され、クライアント側で検証する |
トークンリクエスト
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://example.com/callback
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
トークンレスポンス
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
"scope": "read:user"
}
PKCE(Proof Key for Code Exchange)
PKCE(ピクシー)は、認可コードフローをパブリッククライアントでも安全に利用するための拡張仕様(RFC 7636)。認可コードの横取り攻撃(Authorization Code Interception Attack)を防ぐ。
PKCEの仕組み
sequenceDiagram
participant Client as クライアント
participant AuthServer as 認可サーバー
Note over Client: code_verifier を生成<br/>(43〜128文字のランダム文字列)
Note over Client: code_challenge を計算<br/>(SHA256ハッシュのBase64URL)
Client->>AuthServer: 認可リクエスト<br/>(+ code_challenge, code_challenge_method)
AuthServer-->>Client: 認可コード
Client->>AuthServer: トークンリクエスト<br/>(認可コード + code_verifier)
Note over AuthServer: code_verifierを検証<br/>(ハッシュがcode_challengeと一致するか)
AuthServer-->>Client: アクセストークン
code_verifier と code_challenge
code_verifier = ランダムな43〜128文字の文字列([A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~")
code_challenge = BASE64URL(SHA256(code_verifier))
| パラメータ | 送信タイミング | 説明 |
|---|---|---|
code_challenge | 認可リクエスト時 | code_verifierのSHA256ハッシュ(Base64URL) |
code_challenge_method | 認可リクエスト時 | S256(推奨)または plain |
code_verifier | トークンリクエスト時 | 元のランダム文字列 |
PKCEにより、認可コードを傍受した攻撃者がトークンを取得しようとしても、code_verifier を知らないためトークン交換に失敗する。
PKCE付き認可リクエスト例
GET /authorize?
response_type=code
&client_id=CLIENT_ID
&redirect_uri=https://example.com/callback
&scope=read:user
&state=xyz123
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
ベストプラクティス: OAuth 2.1ドラフトでは、コンフィデンシャルクライアントを含むすべてのクライアントでPKCEの使用が必須とされている。
クライアントクレデンシャルフロー
ユーザーが介在しない、マシン間通信(M2M)向けのフロー。クライアント自身の資格情報でトークンを取得する。
sequenceDiagram
participant Client as クライアント<br/>(バックエンドサービス)
participant AuthServer as 認可サーバー
participant Resource as リソースサーバー
Client->>AuthServer: トークンリクエスト<br/>(client_id + client_secret)
AuthServer->>Client: アクセストークン
Client->>Resource: APIリクエスト<br/>(Bearer トークン)
Resource->>Client: リソース応答
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&scope=api:read
デバイスコードフロー
スマートTVやIoTデバイスなど、ブラウザや十分な入力手段を持たないデバイス向けのフロー(RFC 8628)。
sequenceDiagram
participant Device as デバイス
participant AuthServer as 認可サーバー
participant User as ユーザー<br/>(スマホ/PC)
Device->>AuthServer: デバイス認可リクエスト<br/>(client_id, scope)
AuthServer->>Device: device_code, user_code,<br/>verification_uri
Device->>User: 「https://example.com/device に<br/>コード ABC-123 を入力してください」
User->>AuthServer: verification_uriにアクセスし<br/>user_codeを入力 + 認証
loop ポーリング
Device->>AuthServer: トークンリクエスト<br/>(device_code)
AuthServer->>Device: authorization_pending / トークン
end
アクセストークンとリフレッシュトークン
アクセストークン
リソースサーバーへのアクセスに使用するトークン。
| 特性 | 説明 |
|---|---|
| 有効期限 | 短い(数分〜数時間が一般的) |
| 用途 | APIリクエストのAuthorizationヘッダーに付与 |
| 形式 | 不透明文字列(Opaque Token)またはJWT |
| 送信方法 | Authorization: Bearer <token> |
GET /api/user/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
リフレッシュトークン
アクセストークンの有効期限切れ時に、新しいアクセストークンを取得するためのトークン。
| 特性 | 説明 |
|---|---|
| 有効期限 | 長い(数日〜数週間、または無期限) |
| 用途 | アクセストークンの再取得 |
| 保管場所 | サーバーサイドに安全に保管(ブラウザのlocalStorageは避ける) |
| 送信先 | 認可サーバーのトークンエンドポイントのみ |
トークンリフレッシュフロー
sequenceDiagram
participant Client as クライアント
participant AuthServer as 認可サーバー
participant Resource as リソースサーバー
Client->>Resource: APIリクエスト(期限切れトークン)
Resource->>Client: 401 Unauthorized
Client->>AuthServer: リフレッシュリクエスト<br/>(grant_type=refresh_token)
AuthServer->>Client: 新しいアクセストークン<br/>(+ 新しいリフレッシュトークン)
Client->>Resource: APIリクエスト(新しいトークン)
Resource->>Client: リソース応答
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=dGhpcyBpcyBhIHJlZnJlc2g...
&client_id=CLIENT_ID
トークンの比較
| 項目 | アクセストークン | リフレッシュトークン |
|---|---|---|
| 有効期限 | 短い(分〜時間) | 長い(日〜週) |
| 送信先 | リソースサーバー | 認可サーバーのみ |
| 漏洩時の影響 | 限定的(短期間で失効) | 重大(新しいトークンを無制限に取得可能) |
| 保管要件 | メモリ内が望ましい | サーバーサイドに暗号化保管 |
スコープ
スコープは、アクセストークンに付与される権限の範囲を定義する仕組み。クライアントが必要最小限の権限のみを要求できるようにする(最小権限の原則)。
スコープの設計例
# GitHub API
scope=repo user:email read:org
# Google API
scope=https://www.googleapis.com/auth/calendar.readonly
https://www.googleapis.com/auth/drive.file
# 独自API
scope=read:users write:posts delete:comments
| サービス | スコープ形式 | 例 |
|---|---|---|
| GitHub | リソース:操作 | repo, user:email, read:org |
| URL形式 | https://www.googleapis.com/auth/calendar.readonly | |
| Microsoft | リソース.操作 | User.Read, Mail.Send |
| Slack | リソース:操作 | channels:read, chat:write |
同意画面(Consent Screen)
ユーザーがクライアントに権限を付与する際、認可サーバーは要求されたスコープの一覧を同意画面として表示する。ユーザーはスコープの内容を確認し、アクセスを許可または拒否できる。
セキュリティ上の注意点
必須対策
| 対策 | 説明 |
|---|---|
| HTTPS通信の強制 | トークンの漏洩を防ぐため、すべての通信をTLSで暗号化する |
| stateパラメータの使用 | CSRF攻撃を防ぐため、認可リクエストにランダムなstate値を含め、コールバックで検証する |
| PKCEの使用 | 認可コード横取り攻撃を防止する。パブリッククライアントでは必須 |
| redirect_uriの厳密な一致 | オープンリダイレクト攻撃を防ぐため、事前登録されたURIと完全一致で検証する |
| トークンの安全な保管 | アクセストークンはメモリ内、リフレッシュトークンはサーバーサイドに保管する |
避けるべきプラクティス
| アンチパターン | リスク | 代替策 |
|---|---|---|
| localStorageにトークンを保存 | XSS攻撃でトークンが漏洩する | HttpOnly + Secure + SameSite属性のCookieを使用 |
| インプリシットグラントの使用 | トークンがURLフラグメントに露出する | 認可コード + PKCEを使用 |
| スコープの過剰な要求 | 侵害時の影響範囲が拡大する | 必要最小限のスコープを要求する |
| トークンの長期有効期限 | 漏洩時のリスクが増大する | 短い有効期限 + リフレッシュトークンで更新する |
| クライアントシークレットのハードコード | ソースコードからシークレットが漏洩する | 環境変数やシークレットマネージャーを使用する |
トークンの失効(Revocation)
不要になったトークンは、トークン失効エンドポイント(RFC 7009)で明示的に無効化する。
POST /revoke HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
token=ACCESS_TOKEN_OR_REFRESH_TOKEN
&token_type_hint=refresh_token
&client_id=CLIENT_ID
OpenID Connect(OIDC)との関係
OpenID Connect(OIDC)は、OAuth 2.0の上に認証レイヤーを追加した仕様。OAuth 2.0が「何ができるか(認可)」を扱うのに対し、OIDCは「誰であるか(認証)」を扱う。
| 項目 | OAuth 2.0 | OpenID Connect |
|---|---|---|
| 目的 | 認可(リソースへのアクセス許可) | 認証(ユーザーの身元確認) |
| 取得するトークン | アクセストークン | アクセストークン + IDトークン |
| ユーザー情報 | 標準化されていない | UserInfoエンドポイントで標準化 |
| スコープ | サービス固有 | openid, profile, email 等が標準定義 |
OIDCでは認可リクエストのスコープに openid を含めることで、IDトークン(JWT形式)が返される。IDトークンにはユーザーの識別子(subクレーム)などが含まれる。
GET /authorize?
response_type=code
&client_id=CLIENT_ID
&scope=openid profile email
&redirect_uri=https://example.com/callback
&state=xyz123
SSO(シングルサインオン)やOIDCの詳細については SSO(シングルサインオン)入門 も参照。
主要サービスのOAuth 2.0エンドポイント
| サービス | 認可エンドポイント | トークンエンドポイント |
|---|---|---|
https://accounts.google.com/o/oauth2/v2/auth | https://oauth2.googleapis.com/token | |
| GitHub | https://github.com/login/oauth/authorize | https://github.com/login/oauth/access_token |
| Microsoft | https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize | https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token |