- ネットワーク
- 技術 💻
401と403の違い|HTTPステータスコードの使い分け判断フロー
401 Unauthorizedは「あなたが誰かわからない(認証されていない・認証情報が無効)」、403 Forbiddenは「あなたが誰かはわかったが、その操作は許可されていない」ときに返すステータスコードです。見分け方はシンプルで、ログインし直せば解決する可能性があるなら401、ログインし直しても結果が変わらないなら403です。
この記事では、HTTPの仕様(RFC 9110)に基づく401と403の定義に加えて、401で必須のヘッダー、存在を隠すために404を返す判断、そして400・404・409・422・429を含めた「どのコードを返すか」の判断フローを、ケース別の早見表で解説します。
401と403の違い
| 401 Unauthorized | 403 Forbidden | |
|---|---|---|
| 意味 | 認証されていない | 認可されていない(権限がない) |
| サーバーの認識 | あなたが誰かわからない | あなたが誰かはわかっている |
| 典型例 | ログインしていない、トークンの期限切れ | 一般ユーザーが管理者用の機能を使おうとした |
| クライアントの次の行動 | ログイン・トークンの再取得 | 再ログインしても変わらない。権限の申請など |
| 必須のヘッダー | WWW-Authenticate | なし |
「認証」と「認可」
この2つのコードは、認証(Authentication)と認可(Authorization)の違いに対応しています。
- 認証 — 「あなたは誰か」を確かめること(ログイン、トークンの検証)
- 認可 — 「あなたにその操作をする権限があるか」を判断すること
ビルの入館にたとえると、401は入口で社員証を持っていない(または期限切れの)状態、403は社員証で本人確認はできたが、そのフロアへの入室権限がない状態です。
名前が紛らわしい理由
401の名前は「Unauthorized(認可されていない)」ですが、実際の意味は「認証されていない」です。これは仕様が作られた当時の命名がそのまま残っているためで、名前ではなく定義で覚えるのが確実です。
仕様(RFC 9110)での定義
HTTPの現行の仕様であるRFC 9110では、次のように定義されています(要約)。
- 401:リクエストに、対象のリソースに対する有効な認証情報がないため、適用されなかった。サーバーはWWW-Authenticateヘッダーを必ず含める
- 403:サーバーはリクエストを理解したが、実行を拒否した。認証情報が提示されていた場合、サーバーはそれをアクセスに不十分と判断している
- また、禁止されたリソースの存在自体を隠したい場合、403の代わりに404を返してもよいとされています
401にはWWW-Authenticateヘッダーが必須
401を返すときは、どの方式で認証すればよいかをWWW-Authenticateヘッダーで伝えます。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Bearerトークン(OAuth 2.0のアクセストークンなど)を使うAPIでは、RFC 6750でエラーの種類の伝え方が決められています。
| 状況 | ステータス | WWW-Authenticateの例 |
|---|---|---|
| トークンが付いていない | 401 | Bearer realm="api" |
| トークンが無効・期限切れ | 401 | Bearer error="invalid_token" |
| トークンは有効だが、スコープ(権限の範囲)が足りない | 403 | Bearer error="insufficient_scope" |
| リクエストの形式が不正 | 400 | Bearer error="invalid_request" |
「トークンは有効だが権限が足りない」は403です。この表は、401と403の線引きをそのまま表しています。
なお、WWW-Authenticate: Basic を返すと、ブラウザはユーザー名とパスワードの入力ダイアログを表示します。SPAのAPIなどでダイアログを出したくない場合は、Bearerなど別の方式を指定します。
403の代わりに404を返すという判断
権限のないユーザーに403を返すと、「そのリソースは存在する」ことが相手に伝わります。
例えば、/orders/12345 に他人がアクセスして403が返れば、「注文12345は存在する」とわかります。IDを順に変えて試すことで、存在する注文の数や番号の傾向が推測できてしまいます。
そのため、次のような場合は404を返してリソースの存在自体を隠す設計がよく採用されます。
- 他のユーザーの注文・請求書・メッセージなど、本人以外には存在も知られたくないリソース
- 非公開のリポジトリや、招待制のページ
大手のサービスでも、権限のない非公開リソースには404を返す設計が見られます。一方、社内の管理画面のように、存在が知られても問題なく、「権限がない」と伝えた方が利用者が迷わない場合は403が適しています。
| 返すコード | 向いているケース |
|---|---|
| 403 | 存在を知られても問題ない。権限がないことを利用者に伝えたい |
| 404 | 存在自体を隠したい。IDの推測による情報収集を防ぎたい |
どのコードを返すか:判断フロー
リクエストを受けてから、次の順に判断すると、返すコードがぶれません。
- リクエストの形式として読めない(JSONが壊れている、必須のパラメータがない) → 400 Bad Request
- 認証情報がない・無効・期限切れ → 401 Unauthorized(WWW-Authenticateを付ける)
- 対象のリソースが存在しない、または存在を隠したい → 404 Not Found
- 認証はできたが、その操作の権限がない → 403 Forbidden
- 形式は正しいが、内容が業務ルールに合わない(メールアドレスの形式違反、在庫数を超える注文) → 422 Unprocessable Content(または400)
- 現在の状態と衝突する(登録済みのメールアドレス、他の人が先に更新した) → 409 Conflict
- リクエストが多すぎる → 429 Too Many Requests(Retry-Afterを付ける)
認証(2)を存在確認(3)より先に判定するのは、ログインしていない人に、リソースの有無を教えないためです。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「401と403の違い|HTTPステータスコードの使い分け判断フロー」
ケース別の早見表
| ケース | 返すコード | 補足 |
|---|---|---|
| ログインしていない状態でマイページのAPIを呼んだ | 401 | |
| アクセストークンの有効期限が切れた | 401 | クライアントはリフレッシュトークンで再取得する |
| 署名が改ざんされたトークン | 401 | |
| 一般ユーザーが管理者用APIを呼んだ | 403 | |
| 他人の注文詳細にアクセスした | 404(または403) | 存在を隠すなら404 |
| 退会済み・停止中のアカウントでアクセスした | 403 | 認証は通るが利用が許可されていない |
| 無料プランのユーザーが有料機能を使おうとした | 403 | 402 Payment Requiredは将来のための予約で、一般的には使わない |
| IPアドレス制限で拒否した | 403 | |
| CSRFトークンが不正 | 403 | フレームワークによっては独自のコードを使う |
| JSONの構文が壊れている | 400 | |
| メールアドレスの形式が不正 | 422または400 | プロジェクトでどちらかに統一する |
| 同じメールアドレスで既に登録済み | 409 | |
| 楽観ロックで更新が競合した | 409(If-Match使用時は412) | |
| 存在しないURL | 404 | |
| URLは存在するが、そのHTTPメソッドに対応していない | 405 | Allowヘッダーで対応メソッドを伝える |
| Content-Typeが対応外 | 415 | |
| 短時間にリクエストが集中した | 429 | Retry-Afterで再試行までの秒数を伝える |
400と422の使い分け
400と422は、プロジェクトによって使い方が分かれるコードです。
- 400 Bad Request — リクエストが構文として正しくない(JSONが壊れている、型が違う)
- 422 Unprocessable Content — 構文は正しく読めたが、内容が処理できない(バリデーションエラー)
422は、以前はWebDAVの拡張でしたが、2022年のRFC 9110でHTTPの標準に取り込まれました。Ruby on RailsやLaravelなどのフレームワークは、バリデーションエラーに422を使います。一方、すべての入力エラーを400にまとめる設計も広く使われており、どちらかが誤りというわけではありません。
大切なのは、プロジェクト内で統一し、API仕様書に明記することです。
409 Conflictを使う場面
409は、リクエスト自体は正しいが、サーバー上の現在の状態と矛盾するときに使います。
- ユニークであるべき値(メールアドレス・ユーザー名)の重複登録
- 楽観ロックで、他の人が先に更新していた
- 「発送済みの注文をキャンセルする」など、状態の遷移として許されない操作
クライアントは、409を受け取ったら最新の状態を取得し直してから、再度操作するかを判断します。
エラーレスポンスの本文:Problem Details
ステータスコードだけでは、「何が問題だったか」を細かく伝えられません。エラーの詳細を返す標準形式として、RFC 9457(Problem Details for HTTP APIs)があります。
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://example.com/problems/insufficient-permission",
"title": "この操作を行う権限がありません",
"status": 403,
"detail": "請求書の削除には管理者権限が必要です",
"instance": "/invoices/12345"
}
エラーの形式をAPI全体で統一しておくと、クライアント側のエラー処理を共通化できます。ただし、401や404の本文に、内部の情報(存在するユーザー名やスタックトレースなど)を含めないよう注意します。
クライアント側での扱い方
フロントエンドやアプリでは、401と403で次の行動を変えるのがポイントです。
| 受け取ったコード | クライアントの動き |
|---|---|
| 401 | リフレッシュトークンでアクセストークンを再取得し、1回だけリトライ。失敗したらログイン画面へ |
| 403 | リトライしない。「権限がありません」と表示する |
| 404 | 「見つかりません」と表示する |
| 409 | 最新の状態を再取得し、利用者に確認する |
| 429 | Retry-Afterの秒数を待ってからリトライする |
よくある不具合が、403を受け取ったときにログイン画面へ戻してしまうことです。権限がない状態は再ログインしても変わらないため、ログインと403を繰り返すループになります。再ログインで解決するのは401だけ、と覚えておきます。
よくある質問
Q. ログインページへのリダイレクト(302)と401はどう使い分けますか? A. ブラウザで画面を表示するWebページでは、未ログイン時にログインページへ302でリダイレクトするのが一般的です。JSONを返すAPIでは、リダイレクトではなく401を返し、クライアント側でログイン画面へ遷移させます。
Q. パスワードを間違えたときは401ですか? A. ログインAPI(認証情報を受け取って検証するAPI)で認証に失敗した場合、401を返すのが一般的です。ただし、「ユーザーが存在しない」と「パスワードが違う」でメッセージを分けると、登録済みのユーザーを推測されるため、同じメッセージにします。
Q. エラーでも200を返し、本文でエラーを伝える設計はだめですか? A. 動作はしますが、ブラウザ・CDN・監視ツール・HTTPクライアントのライブラリは、ステータスコードで成功と失敗を判断します。エラーを200で返すと、キャッシュされたり、監視でエラーとして検知されなかったりする原因になるため、適切なステータスコードを返すのが基本です。
Q. 403と404のどちらを返すか、チーム内で意見が分かれます。 A. 「そのリソースの存在を知られて困るか」で判断します。個人のデータなど知られて困るものは404、社内の管理機能など存在が知られても問題なく、権限がないことを伝えた方が親切なものは403が向いています。
まとめ
- 401は「誰かわからない」、403は「誰かはわかるが権限がない」。再ログインで解決しうるなら401
- 401にはWWW-Authenticateヘッダーが必須。Bearerトークンのスコープ不足は403
- リソースの存在を隠したい場合は404を返してよい(RFC 9110で認められている)
- 判断は 400 → 401 → 404 → 403 → 422 → 409 → 429 の順で考えるとぶれない
- 400と422はプロジェクトで統一し、エラー本文はProblem Details(RFC 9457)で揃える
- クライアントは401だけ再認証、403はリトライしない
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「401と403の違い|HTTPステータスコードの使い分け判断フロー」
株式会社RJCが携わる保険・金融・決済のシステムでは、「誰が、何を見てよいか」の設計がお客様の安心に直結します。ステータスコード1つにも理由を持たせる設計を、チームで共有しながら開発しています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。