Featured image of post OAuth 2.0とOpenID Connect:モダンな認証ガイド

OAuth 2.0とOpenID Connect:モダンな認証ガイド

OAuth 2.0の認可グラントタイプ、PKCE、OpenID Connectによる認証レイヤー、IDトークンとアクセストークンの違い、リフレッシュトークンローテーション、主要プロバイダー実装までを網羅。

OAuth 2.0とOpenID Connectは、モダンなWeb認証・認可の基盤を形成しています。広く使われているにもかかわらず、これらのプロトコルは誤解され、誤設定されることが多く、予防可能なセキュリティ脆弱性を生み出しています。本ガイドでは、アプリケーションに安全な認証を統合するための基本概念、実装パターン、セキュリティベストプラクティスを解説します。

OAuth 2.0の基礎

OAuth 2.0は認可フレームワークであり、認証プロトコルではありません。この区別は極めて重要です。OAuthはクライアントアプリケーションが保護リソースへの委任アクセスを取得する方法を定義しますが、ユーザーIDの検証方法は規定しません。その役割を担うのがOpenID Connectです。

プロトコルは4つのロールを定義します:

ロール説明
リソースオーナーアクセスを許可できるエンティティエンドユーザー
クライアントアクセスを要求するアプリケーションWebアプリ、モバイルアプリ
認可サーバー認証成功後にトークンを発行Auth0、Keycloak
リソースサーバー保護リソースをホストAPIサーバー

認可コードフローとPKCE

PKCE(Proof Key for Code Exchange)付き認可コードフローは、ほとんどのアプリケーションで推奨されるグラントタイプです。PKCEは、暗号チャレンジを通じて認可リクエストをトークンリクエストにバインドすることで、認可コードインターセプト攻撃を防止します。

// Step 1: code_verifierとcode_challengeの生成
function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString("base64url");
  const challenge = crypto
    .createHash("sha256")
    .update(verifier)
    .digest("base64url");
  return { verifier, challenge };
}

// Step 2: 認可サーバーにユーザーをリダイレクト
const { verifier, challenge } = generatePKCE();
const authUrl = new URL("https://authorization-server.com/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("redirect_uri", CALLBACK_URL);
authUrl.searchParams.set("code_challenge", challenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("state", crypto.randomUUID());

// Step 3: 認可コードをトークンと交換
async function exchangeCode(code, verifier) {
  const response = await fetch("https://authorization-server.com/token", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: CALLBACK_URL,
      client_id: CLIENT_ID,
      code_verifier: verifier,
    }),
  });
  return response.json();
}

stateパラメータはCSRF対策を提供します。PKCEは現在ではサーバーサイドWebアプリケーションでも推奨されており、認可コードインターセプトに対する多層防御を提供します。


グラントタイプ比較

グラントタイプユースケースセキュリティ特性
認可コード + PKCEWeb、モバイル、SPA最強 — 新規アプリに推奨
クライアントクレデンシャルサーバー間、サービスアカウントユーザーコンテキストなし
デバイス認可CLI、IoT、スマートTV別デバイスでのURLアクセスが必要
認可コード(PKCEなし)レガシーサーバーサイドアプリコードインターセプト脆弱性あり
暗黙的(非推奨)レガシーSPA仕様から削除済み

OpenID Connectレイヤー

OpenID Connect(OIDC)は、OAuth 2.0の上にアイデンティティレイヤーを追加します。鍵となる追加要素はid_tokenで、認証されたユーザーに関する検証済みクレームを含むJWTです。

// Node.jsでのIDトークン検証
const jwksClient = require("jwks-rsa");
const jwt = require("jsonwebtoken");

const client = jwksClient({
  jwksUri: "https://authorization-server.com/.well-known/jwks.json",
});

async function verifyIdToken(idToken) {
  const decoded = jwt.decode(idToken, { complete: true });
  const key = await client.getSigningKey(decoded.header.kid);
  const signingKey = key.getPublicKey();

  return jwt.verify(idToken, signingKey, {
    issuer: "https://authorization-server.com",
    audience: CLIENT_ID,
    algorithms: ["RS256"],
  });
}

標準的なIDトークンクレームには、sub(サブジェクト識別子)、iss(発行者)、aud(オーディエンス)、exp(有効期限)、iat(発行日時)、nonce(リプレイ防止)が含まれます。


IDトークンとアクセストークンの違い

IDトークンとアクセストークンの違いは、しばしば混乱の原因となります。IDトークンは認証用です — クライアントにユーザーが誰かを伝えます。アクセストークンは認可用です — クライアントが何を許可されているかをリソースサーバーに伝えます。

IDトークンをAPI認可に使用してはいけません。アクセストークンは、不透明(リソースサーバーがイントロスペクションで検証するランダム文字列)またはJWT(クレームを内包する自己完結型)の形式をとります。


リフレッシュトークンローテーション

リフレッシュトークンローテーションは、リフレッシュ操作ごとに新しいリフレッシュトークンを発行し、古いトークンを無効化するセキュリティメカニズムです。

async function refreshAccessToken(refreshToken) {
  const response = await fetch("https://authorization-server.com/token", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: refreshToken,
      client_id: CLIENT_ID,
    }),
  });
  const tokens = await response.json();
  // 古いリフレッシュトークンは無効化される
  await secureStore.save("refresh_token", tokens.refresh_token);
  return tokens.access_token;
}

Auth0、Okta、Keycloakなどの主要プロバイダーは、デフォルトでリフレッシュトークンローテーションを有効にしています。再利用検出(ローテートされたトークンが再使用された場合に全トークンを無効化)と組み合わせることで、トークン盗難に対する強力な保護を提供します。


セキュリティベストプラクティス

トークンの保存方法は最も重要なセキュリティ判断のひとつです。ブラウザベースのアプリケーションでは、アクセストークンをメモリに保存し、リフレッシュトークンにhttpOnly Cookieを使用します。localStorageは同じオリジンのすべてのJavaScriptからアクセス可能なため、トークンの保存に使用してはいけません。

保存方法アクセストークンリフレッシュトークン
メモリ変数SPAに最適(永続化なし)不適切
httpOnly Cookie非推奨最適 — XSSから保護
localStorageXSS脆弱性ありXSS脆弱性あり

常にリダイレクトURIをサーバーサイドで検証し、短命なアクセストークン(15〜60分)を使用し、リクエストするスコープを機能に必要な最小限に制限してください。


結論

OAuth 2.0とOpenID Connectは強力ですが、微妙なニュアンスを持つプロトコルです。すべての新規アプリケーションにはPKCE付き認可コードフローを使用し、クライアント側でIDトークンを徹底的に検証し、リフレッシュトークンローテーションを実装し、localStorageにトークンを保存しないでください。これらのパターンに従い、各グラントタイプのセキュリティ特性を理解することで、ユーザーフレンドリーで一般的な攻撃に耐性のある認証システムを構築できます。