Laravel『UnauthorizedHttpException』の原因と対処法
- 作成日 2025.12.23
- その他
Laravelで出る「Unauthorized HTTP Exception」は、主に Symfony の例外(Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException)として投げられ、認証情報が不足している・形式が違う・トークンが無効・ガード設定がズレている、といった理由で「認証できない」状態を示すことが多い。発生箇所は、API認証(Bearerトークン、JWT、Sanctum/Passport)、Basic認証、独自ミドルウェア、外部サービスの署名検証などさまざま。ポイントは「401(未認証)なのか」「403(認可拒否)なのか」を分け、どの認証方式・どのガードで失敗しているかを特定すること。
エラーの出方と発生条件(典型例)
よくある形。
Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
Unauthenticated.
Invalid token.
The token has been blacklisted.発生条件の典型:
・Authorizationヘッダ(Bearer)が付いていない/空
・トークンの形式が違う(Bearer ではなく別形式)
・期限切れ・署名不正・失効済みのトークン
・APIなのにwebガードで認証しようとしている(guard/middlewareのズレ)
・SanctumでSPA認証のつもりなのに、ステートレスAPIとして叩いている(Cookie/CSRF不足)
・プロキシ/ロードバランサでAuthorizationヘッダが落ちている
・例外的に「認可」ではなく「認証」で落ちているのに403扱いにしている
まず切り分け:401(未認証)と403(認可拒否)
UnauthorizedHttpException は基本的に「認証できない」側(401)で出ることが多い。
・401:ログインしていない/トークンが無い/無効
・403:ログインはできているが権限がない(AuthorizationExceptionなどが多い)
APIで「権限がない」と「ログインしていない」を混ぜると調査が長引くので、例外の型・HTTPステータスを揃えるのが重要。
原因1:Authorizationヘッダ(Bearer)が付いていない/落ちている
クライアント側の付け忘れが最頻。
# NG(Authorizationが無い)
curl -X GET https://example.com/api/me
# OK(Bearer付き)
curl -H "Authorization: Bearer YOUR_TOKEN" https://example.com/api/me
プロキシ配下でヘッダが落ちるケースもある(Nginx/ApacheでAuthorizationをPHPに渡していない等)。
Laravel側で「受け取れているか」をログで確認すると早い。
logger()->info('auth header', [
'authorization' => request()->header('Authorization'),
]);原因2:ガード設定のズレ(web/api/sanctum等を取り違えている)
同じルートでも、どのガードで認証しているかで必要な情報が変わる。
典型:
・APIを叩いているのに auth:web が付いている
・Sanctumのつもりが auth:api(別ドライバ)になっている
ルートで明示しているミドルウェアを確認する。
// routes/api.php(例)
Route::middleware('auth:sanctum')->get('/me', function () {
return auth()->user();
});Guardが合っていない場合は、意図する方式に合わせて middleware を揃える。
// 例:トークンAPIなら sanctum / passport / jwt など設計に合わせる
Route::middleware('auth:sanctum')->group(function () {
Route::get('/me', ...);
});原因3:トークンが無効(期限切れ・署名不正・失効)
JWTやPassport等では、期限切れ・署名検証失敗・ブラックリスト入りで UnauthorizedHttpException が出ることがある。
発生条件:
・トークンのexp切れ
・秘密鍵/署名キーの不一致(環境差・ローテーション)
・ログアウト等で失効済み
対処の方向性:
・トークンを再発行する導線(リフレッシュトークン/再ログイン)
・環境ごとのキー管理を統一(.env差、config:cache差)
・時刻ズレ(NTP不整合)も疑う(特にコンテナ/VM)
原因4:SanctumのSPA認証でCookie/CSRFが送れていない
Sanctumを「SPA(Cookie)認証」として使う場合、単純なBearerだけではなく、同一ドメイン/サブドメイン、CORS、credentials、CSRF取得などが成立していないと未認証になる。
発生条件の典型:
・別ドメインから叩いているのに credentials を付けていない
・CSRF Cookieを取得していない(/sanctum/csrf-cookie)
・SESSION_DOMAIN や SANCTUM_STATEFUL_DOMAINS の設定不備
フロントからCookie送信する例(概念)。
// fetch例(Cookieを送る)
await fetch('https://api.example.com/sanctum/csrf-cookie', {
credentials: 'include',
});
const res = await fetch('https://api.example.com/api/me', {
credentials: 'include',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
});「Sanctumでトークン方式(personal access token)」を使う場合は Bearer を付ける運用になるため、SPA方式と混在しないよう整理する。
原因5:例外の投げ方が“未認証”になっている(独自ミドルウェア/署名検証)
Webhookや社内APIで、署名(HMAC)検証や独自トークン検証に失敗したときに UnauthorizedHttpException を投げているケース。
発生条件:
・署名ヘッダが無い/不一致
・リプレイ対策のtimestampが古い
Laravel側の例(概念)。
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
public function handle($request, \Closure $next)
{
$sig = $request->header('X-Signature');
if (!$sig || $sig !== hash_hmac('sha256', $request->getContent(), config('app.webhook_secret'))) {
throw new UnauthorizedHttpException('hmac', 'Invalid signature');
}
return $next($request);
}この場合は「何が足りないと落ちるか」をAPI仕様として固定し、ヘッダ名・計算式・エンコーディングを揃えるのが重要。
サンプル:APIで401をJSONで返す(未認証時の応答を統一)
API利用だとHTMLリダイレクトよりJSONが欲しいケースが多い。
// app/Exceptions/Handler.php(例)
use Illuminate\Auth\AuthenticationException;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json([
'message' => 'Unauthenticated',
], 401);
}
return redirect()->guest(route('login'));
}UnauthorizedHttpException(Symfony)を独自に投げている場合も、APIとしての応答形式を揃えると保守が楽になる。
チェックリスト(上から順に確認する)
1) 401(未認証)か403(認可)かを確認したか(例外型とHTTPステータス)
2) Authorization: Bearer がリクエストに付いているか(プロキシ配下で落ちていないか)
3) どのガード/ミドルウェアで認証しているか(auth:web / auth:api / auth:sanctum など)
4) トークンの期限切れ・署名キー不一致・失効がないか(環境差・時刻ズレも含む)
5) SanctumのSPA方式なら Cookie/CSRF/credentials/CORS/設定(SESSION_DOMAIN, STATEFUL_DOMAINS)を満たしているか
6) 独自認証(署名検証等)なら、必要ヘッダと計算方式がクライアントと一致しているか
-
前の記事
Laravel『Model Not Found(ModelNotFoundException)』の原因と対処法 2025.12.22
-
次の記事
Laravel『UnauthorizedHttpException』の原因と対処法 2025.12.24
コメントを書く