認証と認可の違い

まず「認証」と「認可」の違いを理解しましょう。

概念英語質問
認証 (AuthN)Authenticationあなたは誰?ログイン
認可 (AuthZ)Authorization何を許可する?APIアクセスの許可
  • OAuth 2.0 → 認可のフレームワーク(「このアプリにGoogleカレンダーの読み取りを許可する」)
  • OpenID Connect (OIDC) → OAuth 2.0の上に作られた認証レイヤー(「Googleアカウントでログイン」)

OAuth 2.0 の基本

登場人物

役割説明
Resource Ownerリソースの所有者(ユーザー)あなた
ClientリソースにアクセスしたいアプリあなたのWebアプリ
Authorization Server認可を行うサーバーGoogleのOAuthサーバー
Resource Serverリソースを持つサーバーGoogle Calendar API

全体の流れ(Authorization Code Flow)

ユーザー        クライアント      認可サーバー       リソースサーバー
  │                │                │                 │
  │ 1. ログインクリック │                │                 │
  │──────────────→│                │                 │
  │                │ 2. 認可リクエスト  │                 │
  │                │───────────────→│                 │
  │ 3. ログイン画面    │                │                 │
  │←───────────────────────────────│                 │
  │ 4. ログイン+許可   │                │                 │
  │───────────────────────────────→│                 │
  │                │ 5. 認可コード     │                 │
  │                │←──────────────│                 │
  │                │ 6. コード→トークン交換 │                 │
  │                │───────────────→│                 │
  │                │ 7. アクセストークン  │                 │
  │                │←──────────────│                 │
  │                │ 8. APIリクエスト    │                 │
  │                │─────────────────────────────────→│
  │                │ 9. レスポンス      │                 │
  │                │←────────────────────────────────│
  │ 10. 結果表示    │                │                 │
  │←──────────────│                │                 │

OAuth 2.0 のフロー(Grant Type)

1. Authorization Code Flow(認可コードフロー)

最も安全で推奨されるフローです。サーバーサイドアプリケーション向け。

1. ユーザーを認可エンドポイントにリダイレクト
GET https://auth.example.com/authorize?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid profile email
  &state=random_state_value

2. ユーザーがログイン・許可後、コールバックURLにリダイレクト
GET https://yourapp.com/callback?
  code=AUTHORIZATION_CODE
  &state=random_state_value

3. 認可コードをアクセストークンに交換
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

2. Authorization Code Flow + PKCE

SPA(Single Page Application)やモバイルアプリ向け。クライアントシークレットが不要です。

// 1. code_verifier と code_challenge を生成
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

async function generateCodeChallenge(verifier) {
  const hash = await crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(verifier)
  );
  return base64UrlEncode(new Uint8Array(hash));
}

const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);

// 2. 認可リクエスト(code_challenge を含める)
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', generateState());

window.location.href = authUrl.toString();

// 3. コールバックでトークン交換(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: authorizationCode,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    code_verifier: codeVerifier,  // シークレットの代わり
  }),
});

3. Client Credentials Flow

サーバー間通信向け。ユーザーが関与しない。

const tokenResponse = await fetch('https://auth.example.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    scope: 'api:read api:write',
  }),
});

フローの選び方

アプリケーション推奨フロー
サーバーサイドWebアプリAuthorization Code Flow
SPA(React, Vue等)Authorization Code Flow + PKCE
モバイルアプリAuthorization Code Flow + PKCE
サーバー間通信Client Credentials Flow
CLI ツールDevice Authorization Flow

OpenID Connect (OIDC)

OIDCはOAuth 2.0を拡張し、認証機能を追加したものです。

OAuth 2.0 との違い

項目OAuth 2.0OIDC
目的認可認証 + 認可
返されるトークンアクセストークンアクセストークン + IDトークン
ユーザー情報Resource Server から取得IDトークンに含まれる
scope任意openid が必須

IDトークン

OIDCではJWT形式のIDトークンが返されます。

{
  "iss": "https://auth.example.com",
  "sub": "1234567890",
  "aud": "YOUR_CLIENT_ID",
  "exp": 1711900800,
  "iat": 1711897200,
  "nonce": "random_nonce",
  "name": "田中太郎",
  "email": "[email protected]",
  "picture": "https://example.com/photo.jpg"
}

UserInfoエンドポイント

// IDトークン以外の追加情報を取得
const userInfo = await fetch('https://auth.example.com/userinfo', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

JWT(JSON Web Token)の構造

JWTは3つの部分で構成されます。

ヘッダー.ペイロード.署名
xxxxx.yyyyy.zzzzz

ヘッダー

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-123"
}

ペイロード(クレーム)

{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "client-id",
  "exp": 1711900800,
  "iat": 1711897200,
  "scope": "openid profile email"
}

主要なクレーム

クレーム説明
iss発行者(Issuer)
subユーザー識別子(Subject)
aud対象者(Audience)
exp有効期限(Expiration)
iat発行日時(Issued At)
nbf有効開始日時(Not Before)
jtiトークン識別子(JWT ID)

JWTの検証

import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

// JWKS エンドポイントから公開鍵を取得
const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    const signingKey = key.getPublicKey();
    callback(null, signingKey);
  });
}

// トークンを検証
jwt.verify(token, getKey, {
  algorithms: ['RS256'],
  audience: 'YOUR_CLIENT_ID',
  issuer: 'https://auth.example.com',
}, (err, decoded) => {
  if (err) {
    console.error('Token verification failed:', err);
    return;
  }
  console.log('Decoded token:', decoded);
});

トークンの種類と管理

アクセストークン

APIリクエストの認可に使用します。有効期限は短め(15分〜1時間)。

// APIリクエスト時にヘッダーに含める
fetch('https://api.example.com/data', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

リフレッシュトークン

アクセストークンの更新に使用します。有効期限は長め(7日〜30日)。

// アクセストークンが期限切れの場合、リフレッシュトークンで更新
async function refreshAccessToken(refreshToken) {
  const response = await fetch('https://auth.example.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: CLIENT_ID,
    }),
  });

  const data = await response.json();
  return {
    accessToken: data.access_token,
    refreshToken: data.refresh_token,  // ローテーションされる場合がある
  };
}

トークンの保存場所

保存場所セキュリティ用途
HttpOnly Cookieサーバーサイドレンダリング
メモリ(変数)SPA(ページ更新で消える)
sessionStorageSPA(タブ限定)
localStorage×推奨しない(XSSで盗まれる)

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

必須の対策

対策説明
HTTPS を使用トークンの盗聴を防ぐ
state パラメータCSRF攻撃を防ぐ
PKCE認可コードの横取りを防ぐ
nonceリプレイ攻撃を防ぐ(OIDC)
トークンの有効期限短めに設定(アクセストークンは1時間以内)
redirect_uri の厳密な一致オープンリダイレクト攻撃を防ぐ

よくある間違い

// NG: アクセストークンをURLパラメータに含める
fetch(`https://api.example.com/data?token=${accessToken}`);
// → ログやリファラーヘッダーで漏洩する可能性

// OK: ヘッダーに含める
fetch('https://api.example.com/data', {
  headers: { Authorization: `Bearer ${accessToken}` },
});
// NG: IDトークンの検証をスキップ
const user = JSON.parse(atob(idToken.split('.')[1]));

// OK: 署名を検証してからデコード
const verified = await verifyIdToken(idToken);

主要プロバイダーの設定

Google

Authorization Endpoint: https://accounts.google.com/o/oauth2/v2/auth
Token Endpoint: https://oauth2.googleapis.com/token
UserInfo Endpoint: https://openidconnect.googleapis.com/v1/userinfo
JWKS URI: https://www.googleapis.com/oauth2/v3/certs

GitHub

Authorization Endpoint: https://github.com/login/oauth/authorize
Token Endpoint: https://github.com/login/oauth/access_token
UserInfo: https://api.github.com/user

注意: GitHubはOIDCをサポートしていません(OAuth 2.0のみ)。

ディスカバリーエンドポイント

OIDCプロバイダーは .well-known/openid-configuration で設定情報を公開しています。

curl https://accounts.google.com/.well-known/openid-configuration
{
  "issuer": "https://accounts.google.com",
  "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
  "token_endpoint": "https://oauth2.googleapis.com/token",
  "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
  "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
  "scopes_supported": ["openid", "email", "profile"],
  "response_types_supported": ["code", "token", "id_token"]
}

まとめ

概念説明
OAuth 2.0認可のフレームワーク(APIアクセスの許可)
OIDCOAuth 2.0 + 認証(ログイン)
JWTトークンの形式(ヘッダー.ペイロード.署名)
PKCESPA/モバイルアプリ向けのセキュリティ強化
リフレッシュトークンアクセストークンの更新用

OAuth 2.0 / OIDC は複雑に見えますが、基本的な流れを理解すれば怖くありません。実装には Auth.js (NextAuth)、Lucia、Supabase Auth 等の認証ライブラリを使うことで、セキュリティリスクを減らせます。


JWTトークンの中身を確認するには、AssistyのJWTデコーダーが便利です。ブラウザ上でトークンのヘッダーとペイロードを安全にデコードできます。