401と403の違い:認証がないリクエストと拒否されたリクエスト
HTTP の 401 Unauthorized と 403 Forbidden の違いを RFC 9110 と OAuth 2.0 Bearer トークン仕様で整理しました。WWW-Authenticate、再リクエストの規則、404 で存在を隠す場合も扱います。
API を作っていると、ログインしていないリクエストと、ログインはしているがその操作が許可されていないリクエストに、それぞれどのステータスコードを返すか決める必要があります。HTTP の仕様に沿うと、通常は前者が 401、後者が 403 です。ところが 401 の名前が Unauthorized なので、この二つはよく取り違えられます。
この記事では、HTTP の意味論を定めた RFC 9110 と、OAuth 2.0 の Bearer トークンの使い方を定めた RFC 6750 をもとに二つのコードを整理します。
401:有効な認証情報がない
RFC 9110 は 401 を「対象リソースに対する有効な認証資格情報がないため、リクエストは適用されなかった」という意味で定義しています。名前は Unauthorized ですが、定義が扱っているのは認可よりも認証です。リクエストした側が誰なのかを有効に証明できたかどうかが基準です。
401 には必ず守るべき条件が一つあります。401 レスポンスを生成するサーバーは、対象リソースに適用できるチャレンジ(challenge)を一つ以上含む WWW-Authenticate ヘッダーを送らなければなりません(MUST)。チャレンジは、どの認証方式で再試行できるかをクライアントに伝える値です。RFC 6750 には次の例があります。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"
このヘッダーなしで 401 だけを返すのは仕様違反です。
リクエストにすでに資格情報が含まれていた場合、401 はその資格情報が拒否されたことを意味し、クライアントは新しい Authorization ヘッダーを付けてリクエストし直してもかまいません(MAY)。ユーザーエージェントがすでに一度以上認証を試み、同じチャレンジを含む 401 を再び受け取った場合は、レスポンス本文をユーザーに見せるべき(SHOULD)だと仕様は書いています。本文に診断情報が含まれていることがあるためです。
403:理解したが実行しない
403 は、サーバーがリクエストを理解したものの実行を拒否するという意味です。拒否の理由を伝えたい場合、サーバーはレスポンス本文に書くことができます。
403 で WWW-Authenticate を送る義務はありません。ただし RFC 9110 の 11.6.1 節は、別の資格情報を使えばレスポンスが変わりうることを示すため、401 以外のレスポンスでもこのヘッダーを送ってよい(MAY)としています。
リクエストに資格情報が含まれていた場合、サーバーはそれだけではアクセスを許可するのに不十分だと判断したことになります。クライアントは同じ資格情報で自動的に再試行すべきではなく(SHOULD NOT)、新しい、または別の資格情報で試すことはできます(MAY)。
403 がいつも「ログイン済みユーザーの権限不足」だけを意味するわけではありません。RFC 9110 は、資格情報とは関係のない理由で拒否されることもあるとしています。
| 401 Unauthorized | 403 Forbidden | |
|---|---|---|
| RFC 9110 の定義 | 有効な認証資格情報がない | リクエストを理解したが実行を拒否する |
WWW-Authenticate |
必ず送る(MUST) | 義務なし |
| 再リクエスト | 新しい Authorization で再試行してよい(MAY) |
同じ資格情報での自動再試行はしない(SHOULD NOT)。別の資格情報なら可(MAY) |
実務では「認証をやり直せば結果が変わりうるか」という問いが二つを見分ける目安としてよく使われます。ただし、これは RFC が定めた判定手順ではありません。RFC 9110 は各コードの意味を定義し、資格情報がない、無効、または不完全なリクエストにはオリジンサーバーが 401 を返すべき(SHOULD)だとしています。
OAuth の Bearer トークンでは
RFC 6750 は、Bearer トークンで保護されたリソースについてエラーコードとステータスコードを次のように対応させています。
invalid_request:必要なパラメーターがない、またはリクエスト形式が不正 → 400(SHOULD)invalid_token:トークンが期限切れ、失効、形式不正、または別の理由で無効 → 401(SHOULD)insufficient_scope:トークンが持つ以上の権限が必要 → 403(SHOULD)
invalid_token の場合、クライアントは新しいアクセストークンを取得して再試行できます。insufficient_scope の場合、サーバーは必要な scope を示してもかまいません(MAY)。つまり、期限切れのトークンなら 401、トークンは有効でもスコープが足りなければ 403 です。
リクエストに認証情報がまったく含まれていない場合、RFC 6750 は error などのエラー情報を含めるべきではない(SHOULD NOT)としています。クライアントが認証の必要性を知らなかった場合や、サポートされていない方式を試した場合がこれにあたります。上で見た realm だけのチャレンジがその例です。
RFC 6750 は2012年の文書で、RFC Editor では RFC 8996 と RFC 9700 によって更新されたと表示されています。RFC 9700 は OAuth 2.0 のセキュリティに関する現在のベストプラクティスを扱いますが、ここで引用した 401・403・insufficient_scope の対応は変えていません。
存在そのものを隠したいとき:404
RFC 9110 は 403 の節で、禁止されたリソースが現在存在すること自体を隠したいオリジンサーバーは、代わりに 404 を返してもよい(MAY)としています。
404 の定義にも、現在の表現が見つからない場合と、サーバーが表現の存在を明かすつもりがない場合の両方が含まれます。403 はそのアドレスに何かがあることを明かしてしまう可能性があるため、404 という選択肢があるわけです。
404 は、メソッドの定義や明示的なキャッシュ制御で別に定められていなければ、ヒューリスティックにキャッシュされうるレスポンスです。権限が変わったらすぐ見えるべきリソースに 404 を使うなら、キャッシュの動作をヒューリスティックに任せず、望む動作に合わせて方針を明示的に選ぶ必要があります。保存を許すか、共有キャッシュに再利用させてよいかで必要な方針は変わるので、何かしらの Cache-Control を付ければ済むわけではありません。ディレクティブごとの違いは Cache-Control の no-cache と no-store の違い で扱いました。
ブラウザーがログインダイアログを出しうる場合
WHATWG Fetch Standard の HTTP-network-or-cache fetch の手順には、401 を受け取ったときにユーザー名とパスワードを尋ねるステップがあります。外側の条件は次の四つです。
- レスポンスのステータスが 401 である。
- レスポンスのテインティング(response tainting)が
"cors"ではない。 - Fetch の内部値
includeCredentialsが true である。 - リクエストに、ユーザーへの問い合わせに使える traversable(ウィンドウ)がある。
includeCredentials は Cookie や Authorization を実際に送ったかどうかを表す値ではなく、Fetch が内部で計算する真偽値です。リクエストの credentials mode が "include" のとき、または "same-origin" でレスポンスのテインティングが "basic" のときに true になります。Cross-Origin-Embedder-Policy のチェックで false に戻ることもあり、資格情報を一つも送っていなくても true になりえます。
外側の条件を満たしても、すぐに尋ねるわけではありません。内側にもう一つ条件があり、リクエストの use-URL-credentials フラグが設定されていないか、リクエストがすでに認証のための再リクエスト(authentication fetch)である場合にだけ尋ねます。use-URL-credentials は URL に含まれるユーザー名とパスワードを優先させるフラグで、既定では設定されていません。
この手順から直接言えることは限られます。fetch() で別オリジンの API を CORS リクエストとして呼び出し、レスポンスのテインティングが "cors" になった場合、401 を受け取ってもこのステップには入りません。逆に、条件をすべて満たしたからといって、どのブラウザーでもログインダイアログが出るとは限りません。この部分には、複数または欠落した WWW-Authenticate ヘッダーやパースの問題について「Needs testing」という注記があるので、実際の動作はブラウザーごとに確認する必要があります。
プロキシが認証を求めるときは、401 ではなく 407 Proxy Authentication Required と Proxy-Authenticate ヘッダーを使います。
ステータスコードを選ぶ一般的な順序
これは RFC が定めた判定手順ではなく、上の定義と推奨に沿う場合によく使われる基本の選び方です。
- 認証情報がない、または無効 → 401 と
WWW-Authenticate - 誰かは確認できたが、この操作は許可されていない → 403
- リソースがあること自体を知らせたくない → 404 も認められている
403 は資格情報と関係のない理由でも使えます。同じ RFC 9110 の 3xx については 301・302・307・308 リダイレクトの違い で整理しました。
参照した仕様とドキュメント
- RFC 9110 — HTTP Semantics:11.3・11.4・11.6.1 節(認証ヘッダー)、15.5.2 節(401)、15.5.4 節(403)、15.5.5 節(404)、15.5.8 節(407)
- RFC 6750 — OAuth 2.0 Bearer Token Usage:3 節と 3.1 節(エラーコードとステータスコード)
- RFC 9700 — OAuth 2.0 Security Best Current Practice
- RFC 6750 の文書情報(RFC Editor):更新関係
- WHATWG Fetch Standard — HTTP-network-or-cache fetch(Living Standard)
リンクと内容は2026年10月3日に確認しました。Fetch は随時更新される標準です。