メインコンテンツまでスキップ

OpenID Connect と OAuth 2.0

WebアプリケーションにおけるID管理と認可の標準プロトコルについて解説します。

OAuth 2.0 とは

OAuth 2.0 は 認可 (Authorization) のためのオープン標準プロトコルです。

主な目的

  • ユーザーが第三者アプリケーションに自分のリソース(データ)へのアクセス権を付与する
  • パスワードを共有せずに、特定のリソースへのアクセス権限を委譲する

OAuth 2.0 の4つの主要な役割

  1. リソースオーナー (Resource Owner): リソースの所有者(通常はエンドユーザー)
  2. クライアント (Client): リソースにアクセスしたいアプリケーション
  3. 認可サーバー (Authorization Server): アクセストークンを発行するサーバー
  4. リソースサーバー (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のパラメータ:

  1. code_verifier:

    • 43〜128文字のランダムな文字列
    • クライアントがローカルで生成・保持
    • 例: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  2. code_challenge:

    • code_verifierのSHA256ハッシュをBase64URL エンコードしたもの
    • 認可リクエスト時に送信
    • 例: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  3. 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.0OpenID Connect
主な目的認可 (Authorization)認証 (Authentication)
取得できる情報アクセス権限ユーザーのID情報
トークンAccess TokenID 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 の認可コードフローと同じですが、以下の違いがあります:

  1. スコープに openid を含める

    scope=openid profile email
  2. ID Token が発行される

    • Access Token に加えて ID Token を取得
    • ID Token にユーザー情報が含まれる
  3. UserInfo エンドポイント

    • より詳細なユーザー情報を取得可能
    GET /userinfo
    Authorization: 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');
});

セキュリティ上の考慮事項

必須の対策

  1. HTTPS の使用

    • すべての通信は HTTPS で行う
  2. State パラメータ

    • CSRF攻撃を防ぐため、stateパラメータを使用して検証
  3. PKCE (Proof Key for Code Exchange)

    • SPAやモバイルアプリでは必須
    • 認可コードの横取り攻撃を防ぐ
  4. トークンの安全な保管

    • Access Token は適切に保護する
    • ブラウザの LocalStorage には保存しない(XSS対策)
  5. スコープの最小化

    • 必要最小限のスコープのみをリクエスト
  6. トークンの有効期限

    • 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 などの対策が必須

参考リンク