はじめに
「Googleでログイン」ボタンを押すと何が起こるでしょうか?たった1回のクリックの裏では、**OAuth 2.0**という精巧なプロトコルが動いています。
OAuthを理解すれば、ソーシャルログイン、API認証、マイクロサービス間通信、さらにはAnthropicのClaude API認証ポリシーまで明確になります。
核心概念:認証 vs 認可
認証(Authentication): 「あなたは誰?」
→ ユーザーが本人であることを証明
→ ID/パスワード、生体認証、OTP
認可(Authorization): 「何ができる?」
→ 認証されたユーザーに権限を付与
→ このアプリがあなたのGoogle Calendarを読んでもいい?
OAuth 2.0 = 認可(Authorization)フレームワーク
OpenID Connect = OAuth 2.0の上に認証を追加したもの
OAuth 2.0の4つの役割
┌──────────────┐
│ Resource Owner│ ← ユーザー
└──────┬───────┘
│ 「このアプリに私のカレンダーへのアクセスを許可」
▼
┌──────────────┐ ┌──────────────────┐
│ Client │────▶│ Authorization │
│ (私たちのアプリ)│◀────│ Server │
└──────┬───────┘ │ (Google OAuth) │
│ └──────────────────┘
│ Access Token
▼
┌──────────────┐
│ Resource │
│ Server │ ← Google Calendar API
└──────────────┘
Grant Types(認可方式)
1. Authorization Code(最も重要!)
Webアプリで最も多く使われる方式です。
[1] ユーザー → アプリ: 「Googleでログイン」クリック
[2] アプリ → Google: リダイレクト(client_id, redirect_uri, scope)
[3] ユーザー → Google: ログイン + 「許可」クリック
[4] Google → アプリ: redirect_uri?code=AUTH_CODE
[5] アプリ → Google: AUTH_CODE + client_secret → Access Token
[6] アプリ → Google API: Access Tokenでデータ要求
Step 2: Authorization要求URL生成
auth_url = "https://accounts.google.com/o/oauth2/v2/auth?" + urllib.parse.urlencode({
"client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
"redirect_uri": "https://yourapp.com/callback",
"response_type": "code",
"scope": "openid email profile https://www.googleapis.com/auth/calendar.readonly",
"state": "random_csrf_token_abc123", # CSRF防止!
"access_type": "offline", # Refresh Tokenも受け取る
})
→ ユーザーをこのURLにリダイレクト
Step 5: Authorization Code → Access Token交換
token_response = requests.post("https://oauth2.googleapis.com/token", data={
"code": "AUTH_CODE_FROM_CALLBACK",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET", # サーバーサイドのみ!
"redirect_uri": "https://yourapp.com/callback",
"grant_type": "authorization_code",
})
tokens = token_response.json()
{
"access_token": "ya29.xxx...", ← API呼び出し用(1時間)
"refresh_token": "1//xxx...", ← 更新用(永続)
"id_token": "eyJhbGci...", ← ユーザー情報(OIDC)
"expires_in": 3600,
"token_type": "Bearer"
}
Step 6: API呼び出し
calendar = requests.get(
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
headers={"Authorization": f"Bearer {tokens['access_token']}"}
)
**なぜこんなに複雑なのか?**
- Authorization CodeはブラウザURLに露出しますが、**1回限り**で単独では使えません
- Access Token交換は**サーバー間通信**(client_secretを保護)
- フロントエンドにSecretが絶対に露出しません
2. Authorization Code + PKCE(モバイル/SPA必須!)
モバイルアプリやSPA(Reactなど)にはclient_secretを安全に保存する場所がありません。
Clientがcode_verifierを生成(秘密の値)
code_verifier = secrets.token_urlsafe(64)
"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk..."
code_challenge = SHA256(code_verifier) → Base64URL
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()
Authorization要求にchallenge含める
auth_url = f"...&code_challenge={code_challenge}&code_challenge_method=S256"
Token交換時にverifierを提出(Secretの代わり!)
token_response = requests.post("https://oauth2.googleapis.com/token", data={
"code": auth_code,
"client_id": "YOUR_CLIENT_ID",
"code_verifier": code_verifier, # Secretの代わりにこれを検証!
"redirect_uri": "...",
"grant_type": "authorization_code",
})
**PKCEの核心**: Auth Codeを傍受されてもcode_verifierがなければTokenを取得できません。
3. Client Credentials(サーバー間通信)
ユーザーの介入なしにサーバー → サーバー直接認証
例: バックエンド → Google Cloud API
token = requests.post("https://oauth2.googleapis.com/token", data={
"client_id": "SERVICE_CLIENT_ID",
"client_secret": "SERVICE_CLIENT_SECRET",
"grant_type": "client_credentials",
"scope": "https://www.googleapis.com/auth/cloud-platform",
})
4. Refresh Token(更新)
Access Token期限切れ(1時間)→ Refresh Tokenで更新
new_tokens = requests.post("https://oauth2.googleapis.com/token", data={
"refresh_token": "1//xxx...",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "refresh_token",
})
新しいAccess Tokenが発行される(Refresh Tokenは維持)
Token種類の比較
| Token | 有効期間 | 用途 | 保存場所 |
| ------------------ | ------------------ | ---------------------- | ---------------------------------- |
| Authorization Code | 数秒 | Token交換用(1回限り) | URLパラメータ(即時消滅) |
| Access Token | 1時間 | API呼び出し | メモリ(ブラウザ)/ DB(サーバー) |
| Refresh Token | 永続(取消し可能) | Access Token更新 | サーバーDB(安全に!) |
| ID Token (OIDC) | 1時間 | ユーザー情報確認 | メモリ |
JWT(JSON Web Token)の構造
Access TokenとID Tokenは通常JWT形式です:
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik...
├── Header ──────────┤├── Payload ─────────────────────────────...
jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature"
Header
header = json.loads(base64.urlsafe_b64decode(jwt.split('.')[0] + '=='))
{"alg": "RS256", "typ": "JWT"}
Payload
payload = json.loads(base64.urlsafe_b64decode(jwt.split('.')[1] + '=='))
{"sub": "1234567890", "name": "Youngju Kim", "iat": 1709420400}
Signature = RS256(header + "." + payload, private_key)
→ サーバーのpublic keyで検証(改ざん不可)
OpenID Connect (OIDC) — OAuth + 認証
OAuth 2.0だけでは「このユーザーが誰か」は分からない
→ OIDCがid_tokenを追加してユーザー情報を提供
scopeに "openid" を追加 → id_tokenが発行される
scopeに "profile email" を追加 → 名前、メールが含まれる
セキュリティチェックリスト
すべき: stateパラメータでCSRFを防止
すべき: PKCEを使用(SPA/モバイルは必須)
すべき: redirect_uriを正確にマッチ(ワイルドカード不可)
すべき: Refresh Tokenはサーバーにのみ保存
すべき: Access TokenはAuthorizationヘッダーでのみ送信
すべき: HTTPS必須(Token盗取防止)
してはいけない: Access TokenをURLパラメータに入れない
してはいけない: client_secretをフロントエンドに露出しない
してはいけない: TokenをlocalStorageに保存しない(XSS脆弱)
実務例:Anthropic OAuthの問題
最近AnthropicがOAuthポリシーを変更したのもこの文脈です:
以前: Claude Proサブスク → OAuth Token → サードパーティアプリ(OpenClawなど)
変更: OAuth TokenはClaude.ai / Claude Codeでのみ使用可能
理由: Tokenアービトラージ防止(サブスク料 より API Token価値が高い)
代替: Anthropic API Key(従量課金)を使用
**Q1.** OAuth 2.0における認証(Authentication)と認可(Authorization)の違いは?
||認証: ユーザーが誰であるかを確認。認可: 認証されたユーザーに特定リソースへのアクセス権限を付与。OAuth 2.0は認可フレームワークであり、OIDCが認証を追加||
**Q2.** Authorization Code GrantでCodeが1回限りである理由は?
||CodeはブラウザURLに露出するため盗取リスクがある。1回限りでclient_secretなしではToken交換不可のため、単独では無価値||
**Q3.** PKCEにおけるcode_verifierとcode_challengeの関係は?
||code_challenge = SHA256(code_verifier)のBase64URLエンコーディング。認可リクエスト時にchallengeを送り、トークン交換時にverifierを送ってサーバーが検証||
**Q4.** Refresh TokenをlocalStorageに保存してはいけない理由は?
||XSS攻撃でJavaScriptがlocalStorageを読み取れる。Refresh Tokenは長期有効なため、盗取時に持続的なアクセスが可能。httpOnlyクッキーまたはサーバーDBに保存すべき||
**Q5.** Client Credentials Grantはどのような状況で使われるか?
||ユーザーの介入なしにサーバー間の直接認証。例: バックエンドサービスが別のAPIサーバーにアクセスする時。client_id + client_secretのみでToken発行||
**Q6.** stateパラメータがない場合、どのような攻撃が可能か?
||CSRF攻撃 — 攻撃者が自身のアカウントで発行したAuthorization Codeを被害者のコールバックURLに送り、被害者を攻撃者のアカウントにログインさせることができる||
**Q7.** JWTのSignatureは何を保証するか?
||トークンの内容(Header + Payload)が改ざんされていないことを保証。サーバーのPrivate Keyで署名し、Public Keyで検証。ただし暗号化ではない — PayloadはBase64デコードで誰でも読める||
クイズ
OAuth 2.0の動作原理からAuthorization Code、PKCE、Refresh Token、OpenID
Connectまで。なぜGoogleログインボタン1つにこれほど複雑なプロトコルが必要なのか、コードとシーケンスで完全に解剖します。
1. Authorization Code(最も重要!) Webアプリで最も多く使われる方式です。 なぜこんなに複雑なのか?
Authorization CodeはブラウザURLに露出しますが、1回限りで単独では使えません Access
Token交換はサーバー間通信(client_secretを保護) フロントエンドにSecretが絶対に露出しません 2.
Authorization Code + PKCE(モバイル/SPA必須!)
モバイルアプリやSPA(Reactなど)にはclient_secretを安全に保存する場所がありません。
Access TokenとID Tokenは通常JWT形式です:
最近AnthropicがOAuthポリシーを変更したのもこの文脈です: Q1. OAuth
2.0における認証(Authentication)と認可(Authorization)の違いは? Q2. Authorization Code
GrantでCodeが1回限りである理由は? Q3. PKCEにおけるcode_verifierとcode_challengeの関係は? Q4.
Refresh TokenをlocalStorageに保存してはいけない理由は? Q5. Client Credentials
Grantはどのような状況で使われるか? Q6.
현재 단락 (1/140)
「Googleでログイン」ボタンを押すと何が起こるでしょうか?たった1回のクリックの裏では、**OAuth 2.0**という精巧なプロトコルが動いています。