HTTP 401 と 403 の違い・使い分け
レビューでよく指摘されるのが 401 Unauthorized と 403 Forbidden の 取り違えです。RFC 9110 の定義に沿えば迷いません。
The canonical rule: 401 means "who are you?" (authentication), 403 means "I know who you are, but you may not" (authorization).
TL;DR
- 401: 認証されていない / 認証失敗。
WWW-Authenticateヘッダ必須 - 403: 認証済みだが権限なし。再認証しても解決しない
- リソースの存在を隠したい場合は 404 を返す
1. 正しい使い分け表 / When to use
| 状況 / Situation | 返すコード / Status | 補足 / Notes |
|---|---|---|
| ログインしていない | 401 | WWW-Authenticate: Bearer |
| トークン期限切れ | 401 | error="invalid_token" |
| トークン署名不正 | 401 | 認証自体が失敗 |
| ログイン済み・権限不足 | 403 | ロール外のリソース |
| 料金プラン未加入 | 403 (または 402) | 402 は実質未定着 |
| private リポジトリ | 404 | 存在を隠す設計 |
2. RFC 9110 の定義 / RFC definition
401 Unauthorized: "the request has not been applied because it lacks valid authentication credentials for the target resource."
403 Forbidden: "the server understood the request but refuses to authorize it."
401 では認証方式を示すため WWW-Authenticate を 必ず付ける義務があります。 付けないと仕様違反です。
3. 実装例 / Examples
Express (Node.js)
// 401 — 認証必要
res.set("WWW-Authenticate", 'Bearer realm="api"').status(401).json({ error: "unauthenticated" });
// 403 — 権限なし
res.status(403).json({ error: "forbidden", reason: "role:admin required" });FastAPI (Python)
from fastapi import HTTPException
raise HTTPException(
status_code=401,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"},
)4. 間違いやすいパターン / Anti-patterns
- 403 に
WWW-Authenticateを付ける(RFC 上は 401 用) - トークン期限切れで 403 を返す(再ログインで解決するので 401 が正)
- ブラウザのログインダイアログを避けるためにすべて 403 にする(クライアントのリトライ戦略が崩れる)
5. English summary
Per RFC 9110, return 401when the client isn't authenticated (no token, expired token, invalid signature) — and include a WWW-Authenticate header so clients know what scheme to use. Return 403when the client is authenticated but lacks permission; re-authenticating won't help. If you need to hide the existence of a resource (private repos, internal records), return 404to avoid leaking metadata.