認証(Authentication)と認可(Authorization)
混同されやすい2つの概念を最初に整理します。
| 概念 | 問い | 例 |
|---|---|---|
| 認証(Authentication / AuthN) | あなたは誰ですか? | ログイン、パスワード確認 |
| 認可(Authorization / AuthZ) | あなたは何をしてよいですか? | 管理者のみ削除可能 |
認証なしに認可は成立せず、認可なしでは認証後の権限制御ができません。
セッション認証
ブラウザ向け Web アプリの伝統的な方式です。
1. ユーザーがID/PW送信
2. サーバーがID/PWを検証
3. サーバーがセッションIDを生成してDBに保存
4. レスポンスのSet-CookieでセッションIDをブラウザに渡す
5. 以降のリクエストではCookieのセッションIDでユーザーを識別
# セッション認証の実装イメージ(Flask)
from flask import session
import secrets
@app.route('/login', methods=['POST'])
def login():
user = verify_credentials(request.form['email'], request.form['password'])
if user:
session_id = secrets.token_hex(32)
db.sessions.set(session_id, user.id, ex=86400) # 24時間
response.set_cookie('session_id', session_id,
httponly=True, secure=True, samesite='Strict')
return redirect('/')
return 'Invalid credentials', 401
特徴:
- サーバーがセッション状態を保持(ステートフル)
- スケールアウト時にセッション共有が必要(Redis 等)
- ブラウザが自動でCookieを送るためCSRF対策が必要
JWT(JSON Web Token)
ステートレスな認証トークン。サーバー側にセッションを保存せず、トークン自体に情報を持たせます。
JWTの構造
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 ← Header(Base64)
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpob24gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ ← Payload(Base64)
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature(HMAC/RSA)
Payload の例:
{
"sub": "user_123",
"name": "田中太郎",
"role": "admin",
"iat": 1716000000,
"exp": 1716086400
}
# JWTの生成と検証(PyJWT)
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key" # 実際は環境変数から取得
def create_token(user_id: str, role: str) -> str:
payload = {
"sub": user_id,
"role": role,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=1),
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def verify_token(token: str) -> dict:
try:
return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
raise ValueError("Token expired")
except jwt.InvalidTokenError:
raise ValueError("Invalid token")
JWTの注意点
❌ NG: ペイロードに機密情報を入れる
→ Base64はエンコードであって暗号化ではない。誰でも復号して読める
❌ NG: アルゴリズムを "none" に設定する
→ 署名なしでトークンが偽造できる
❌ NG: JWTをLocalStorageに保存する
→ XSS攻撃でJSからアクセス可能。HttpOnlyクッキーに保存する方が安全
✅ 推奨: 有効期限を短く(アクセストークン: 15分〜1時間)
✅ 推奨: リフレッシュトークンと組み合わせる
✅ 推奨: 本番では RS256(非対称鍵)を使う
アクセストークンとリフレッシュトークン
アクセストークン(短命: 15分〜1時間)
→ API認証に使う。有効期限が短いので漏洩リスクが低い
リフレッシュトークン(長命: 7日〜30日)
→ 新しいアクセストークンを取得するためだけに使う
→ DBに保存し、使用済みにしてローテーション(RTR: Refresh Token Rotation)
OAuth 2.0
「認可の委譲フレームワーク」です。「Googleでログイン」「GitHubでログイン」の裏で動いています。
登場人物
| 役割 | 説明 | 例 |
|---|---|---|
| Resource Owner | リソースの所有者 | ユーザー |
| Client | リソースにアクセスしたいアプリ | あなたの作ったアプリ |
| Authorization Server | 認証・認可を行うサーバー | Google, GitHub, Cognito |
| Resource Server | 保護されたリソースを提供 | Google APIs, GitHub API |
Authorization Code フロー(推奨)
1. ユーザーが「Googleでログイン」をクリック
2. ClientがGoogle認可サーバーへリダイレクト
/?response_type=code&client_id=xxx&redirect_uri=xxx&scope=email
3. ユーザーがGoogleでログインして許可
4. Google → Clientへ認可コード(code)をリダイレクトで返す
/callback?code=AUTH_CODE
5. Client(バックエンド)がcodeをアクセストークンと交換
POST /token { code, client_id, client_secret, ... }
← { access_token, refresh_token, expires_in }
6. ClientがアクセストークンでGoogle APIを呼び出す
GET /userinfo Authorization: Bearer ACCESS_TOKEN
# Pythonでのシンプルな実装例(requests-oauthlib)
from requests_oauthlib import OAuth2Session
GOOGLE_CLIENT_ID = "..."
GOOGLE_CLIENT_SECRET = "..."
REDIRECT_URI = "https://app.example.com/callback"
SCOPE = ["openid", "email", "profile"]
# Step 1: 認可URLを生成
oauth = OAuth2Session(GOOGLE_CLIENT_ID, scope=SCOPE, redirect_uri=REDIRECT_URI)
auth_url, state = oauth.authorization_url(
"https://accounts.google.com/o/oauth2/auth"
)
# Step 2: コードをトークンと交換
token = oauth.fetch_token(
"https://oauth2.googleapis.com/token",
client_secret=GOOGLE_CLIENT_SECRET,
authorization_response=callback_url,
)
# Step 3: ユーザー情報を取得
userinfo = oauth.get("https://www.googleapis.com/oauth2/v3/userinfo").json()
OpenID Connect(OIDC)
OAuth 2.0 は認可のプロトコルです。認証(ユーザーが誰か)を扱うために OAuth 2.0 を拡張したのが OpenID Connect です。
OAuth 2.0: 「このユーザーの代わりにGitHubリポジトリを読んでいい」(認可)
OIDC: 「このユーザーはGoogle認証済みの user@example.com だ」(認証)
OIDC では OAuth 2.0 のアクセストークンに加えて ID Token(JWT形式) が発行されます。
// IDトークンのペイロード例
{
"iss": "https://accounts.google.com", // 発行者
"sub": "110169484474386276334", // ユーザーの一意ID
"email": "user@example.com",
"email_verified": true,
"name": "田中太郎",
"picture": "https://lh3.googleusercontent.com/...",
"iat": 1716000000,
"exp": 1716003600
}
認証方式の選択基準
ブラウザ向けWebアプリ(自社サービス)
→ セッション認証 or OIDC(SSO)
スマホアプリ / SPA からのAPI呼び出し
→ JWT(アクセストークン + リフレッシュトークン)
「〇〇でログイン」(ソーシャルログイン)
→ OAuth 2.0 + OIDC
B2B SaaS(企業SSO)
→ SAML 2.0 or OIDC
マイクロサービス間の通信
→ mTLS または JWT(サービスアカウント)
多要素認証(MFA)
知識要素(Something you know): パスワード、PIN
所持要素(Something you have): スマホアプリ(TOTP: Google Authenticator)
生体要素(Something you are): 指紋、顔認識
TOTPの仕組み:
1. サーバーとユーザーが共有秘密鍵を持つ(QRコードで設定)
2. 現在時刻(30秒単位)と秘密鍵から6桁コードを生成
3. コードはサーバー側でも同じ計算で検証
→ 秘密鍵なしでは予測不可能
まとめ
| 方式 | ステートフル | スケール | 主な用途 |
|---|---|---|---|
| セッション | ✅ | Redis必要 | 従来型Webアプリ |
| JWT | ❌(ステートレス) | しやすい | SPA/モバイルAPI |
| OAuth 2.0 | - | - | サードパーティ認可 |
| OIDC | - | - | SSO・ソーシャルログイン |
認証・認可は絶対に自作しないのが原則です。実績ある認証ライブラリやマネージドサービス(Auth0、Firebase Auth、AWS Cognito)を活用しましょう。
ログイン後の権限制御を詳しく学ぶには、認可と権限設計もあわせて確認してください。