- Authors
- Name
- はじめに
- 核心概念:認証 vs 認可
- OAuth 2.0の4つの役割
- Grant Types(認可方式)
- Token種類の比較
- JWT(JSON Web Token)の構造
- OpenID Connect (OIDC) — OAuth + 認証
- セキュリティチェックリスト
- 実務例:Anthropic OAuthの問題

はじめに
「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生成
import urllib.parse
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交換
import requests
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を安全に保存する場所がありません。
import hashlib
import base64
import secrets
# 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 ─────────────────────────────...
import base64
import json
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(従量課金)を使用
クイズ — OAuth 2.0(クリックして確認!)
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デコードで誰でも読める||