認証と認可の違い
まず「認証」と「認可」の違いを理解しましょう。
| 概念 | 英語 | 質問 | 例 |
|---|---|---|---|
| 認証 (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.0 | OIDC |
|---|---|---|
| 目的 | 認可 | 認証 + 認可 |
| 返されるトークン | アクセストークン | アクセストークン + 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(ページ更新で消える) |
| sessionStorage | △ | SPA(タブ限定) |
| 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);
主要プロバイダーの設定
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アクセスの許可) |
| OIDC | OAuth 2.0 + 認証(ログイン) |
| JWT | トークンの形式(ヘッダー.ペイロード.署名) |
| PKCE | SPA/モバイルアプリ向けのセキュリティ強化 |
| リフレッシュトークン | アクセストークンの更新用 |
OAuth 2.0 / OIDC は複雑に見えますが、基本的な流れを理解すれば怖くありません。実装には Auth.js (NextAuth)、Lucia、Supabase Auth 等の認証ライブラリを使うことで、セキュリティリスクを減らせます。
JWTトークンの中身を確認するには、AssistyのJWTデコーダーが便利です。ブラウザ上でトークンのヘッダーとペイロードを安全にデコードできます。