OpenID Connect と OAuth 2.0
WebアプリケーションにおけるID管理と認可の標準プロトコルについて解説します。
OAuth 2.0 とは
OAuth 2.0 は 認可 (Authorization) のためのオープン標準プロトコルです。
主な目的
- ユーザーが第三者アプリケーションに自分のリソース(データ)へのアクセス権を付与する
- パスワードを共有せずに、特定のリソースへのアクセス権限を委譲する
OAuth 2.0 の4つの主要な役割
- リソースオーナー (Resource Owner): リソースの所有者(通常はエンドユーザー)
- クライアント (Client): リソースにアクセスしたいアプリケーション
- 認可サーバー (Authorization Server): アクセストークンを発行するサーバー
- リソースサーバー (Resource Server): 保護されたリソースを提供するサーバー
OAuth 2.0 の主なフロー
認可コードフロー (Authorization Code Flow)
最も安全で一般的に推奨されるフローです。
特徴:
- フロントエンドに機密情報(アクセストークン)を直接渡さない
- バックエンドでトークン交換を行うため安全性が高い
- SPAやネイティブアプリでも PKCE 拡張と組み合わせて利用可能
認可コードフロー + PKCE (Authorization Code Flow with PKCE)
PKCE (Proof Key for Code Exchange) は、OAuth 2.0 の拡張仕様で、認可コードの横取り攻撃を防ぐためのセキュリティ強化手法です。
PKCEが必要な理由:
- SPAやモバイルアプリではclient_secretを安全に保管できない
- 認可コードが傍受される可能性がある
- パブリッククライアント(クライアントシークレットを持たないクライアント)のセキュリティを強化
PKCE の仕組み:
PKCEのパラメータ:
-
code_verifier:
- 43〜128文字のランダムな文字列
- クライアントがローカルで生成・保持
- 例:
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
-
code_challenge:
- code_verifierのSHA256ハッシュをBase64URL エンコードしたもの
- 認可リクエスト時に送信
- 例:
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
-
code_challenge_method:
S256(SHA256推奨) またはplain(非推奨)
実装例:
// 1. code_verifierの生成
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
// 2. code_challengeの生成
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
// Base64URL エンコード
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// 3. 認可リクエスト
async function startAuthFlow() {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// code_verifierをセッションストレージに保存
sessionStorage.setItem('code_verifier', codeVerifier);
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', generateRandomState());
window.location.href = authUrl.toString();
}
// 4. コールバック処理(トークン取得)
async function handleCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
// stateの検証
if (state !== sessionStorage.getItem('state')) {
throw new Error('Invalid state');
}
// 保存しておいたcode_verifierを取得
const codeVerifier = sessionStorage.getItem('code_verifier');
// トークンエンドポイントへリクエスト
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your-client-id',
code_verifier: codeVerifier, // PKCEのキーパラメータ
}),
});
const tokens = await tokenResponse.json();
// code_verifierをクリーンアップ
sessionStorage.removeItem('code_verifier');
sessionStorage.removeItem('state');
return tokens;
}
PKCEの利点:
- ✅ client_secretが不要(SPAやモバイルアプリに最適)
- ✅ 認可コードの横取り攻撃を防ぐ
- ✅ 攻撃者がコードを傍受しても、code_verifierがなければトークンを取得できない
- ✅ すべてのOAuth 2.0クライアントで推奨される標準
セキュリティのポイント:
- code_verifierは十分にランダムで予測不可能である必要がある
- code_verifierはクライアント側でのみ保持し、送信しない(トークン交換時のみ)
- code_challengeは認可サーバー側で安全に保存される
- code_challenge_methodは
S256を使用する(plainは非推奨)
インプリシットフロー (Implicit Flow)
※ 現在は非推奨。SPAの場合は認可コードフロー + PKCE を使用すべきです。
クライアントクレデンシャルフロー (Client Credentials Flow)
サーバー間通信で使用されるフローです。
クライアント → 認可サーバー: client_id + client_secret
認可サーバー → クライアント: アクセストークン
クライアント → リソースサーバー: アクセストークンでAPIアクセス
用途:
- マイクロサービス間通信
- バッチ処理
- ユーザーコンテキストが不要なAPI呼び出し
OpenID Connect (OIDC) とは
OpenID Connect は OAuth 2.0 の上に構築された 認証 (Authentication) レイヤーです。
OAuth 2.0 との違い
| 項目 | OAuth 2.0 | OpenID Connect |
|---|---|---|
| 主な目的 | 認可 (Authorization) | 認証 (Authentication) |
| 取得できる情報 | アクセス権限 | ユーザーのID情報 |
| トークン | Access Token | ID Token + Access Token |
| ユースケース | API アクセス権の委譲 | ログイン・SSO |
ID Token
OpenID Connect の中核となる概念が ID Token です。
- JWT (JSON Web Token) 形式
- ユーザーの認証情報を含む
- 署名されており改ざん検証が可能
ID Token の構造例:
{
"iss": "https://auth.example.com",
"sub": "user123",
"aud": "client-app-id",
"exp": 1735689600,
"iat": 1735686000,
"name": "山田太郎",
"email": "yamada@example.com",
"email_verified": true
}
主なクレーム:
iss: トークンの発行者sub: ユーザーの一意識別子aud: トークンの対象クライアントexp: 有効期限iat: 発行時刻
OpenID Connect のフロー
基本的には OAuth 2.0 の認可コードフローと同じですが、以下の違いがあります:
-
スコープに
openidを含めるscope=openid profile email -
ID Token が発行される
- Access Token に加えて ID Token を取得
- ID Token にユーザー情報が含まれる
-
UserInfo エンドポイント
- より詳細なユーザー情報を取得可能
GET /userinfoAuthorization: Bearer {access_token}
実装例
クライアント側の認可リクエスト
// 認可エンドポイントへリダイレクト
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', generateRandomState()); // CSRF対策
window.location.href = authUrl.toString();
バックエンドでのトークン取得
// コールバックで認可コードを受け取る
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// stateの検証 (CSRF対策)
if (state !== req.session.state) {
return res.status(400).send('Invalid state');
}
// トークンエンドポイントへリクエスト
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your-client-id',
client_secret: 'your-client-secret',
}),
});
const tokens = await tokenResponse.json();
// tokens.access_token, tokens.id_token, tokens.refresh_token
// ID Tokenを検証
const idToken = verifyJWT(tokens.id_token);
// セッションに保存
req.session.userId = idToken.sub;
req.session.accessToken = tokens.access_token;
res.redirect('/dashboard');
});
セキュリティ上の考慮事項
必須の対策
-
HTTPS の使用
- すべての通信は HTTPS で行う
-
State パラメータ
- CSRF攻撃を防ぐため、stateパラメータを使用して検証
-
PKCE (Proof Key for Code Exchange)
- SPAやモバイルアプリでは必須
- 認可コードの横取り攻撃を防ぐ
-
トークンの安全な保管
- Access Token は適切に保護する
- ブラウザの LocalStorage には保存しない(XSS対策)
-
スコープの最小化
- 必要最小限のスコープのみをリクエスト
-
トークンの有効期限
- Access Token には短い有効期限を設定
- Refresh Token で更新する仕組みを実装
主要なプロバイダー
- Azure Active Directory (Microsoft Entra ID)
- Auth0
- Okta
- Google Identity Platform
- Amazon Cognito
- Keycloak (オープンソース)
まとめ
- OAuth 2.0: API アクセスの認可フレームワーク
- OpenID Connect: OAuth 2.0 ベースの認証プロトコル
- 認可コードフロー: 最も安全で推奨されるフロー
- ID Token: ユーザー認証情報を含むJWT
- セキュリティ: HTTPS、State、PKCE などの対策が必須