1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フロントからAPIを叩くと「CORS policyによりブロックされました」という表示。
これはLaravelでAPI、Next.jsでフロントを実装した時に、よく遭遇するエラーです。

今回はCORSとはそもそもなにか、どうすればそのエラーを回避できるかについてまとめます。

CORSとは?

CORS(Cross-Origin Resource Sharing) は、「オリジン(= スキーム + ホスト名 + ポート)が異なる相手にブラウザがアクセスする際の許可ルール」です。ブラウザは必要に応じてプリフライト(OPTIONS リクエストを飛ばし、サーバからの許可ヘッダを確認してから本リクエストを送ります。

オリジン例

  • APIサーバー: http://localhost:8000

  • フロント(Next.js): http://localhost:3000

これらはオリジンが異なるためCORSが発動します。サーバーが適切なヘッダを返さないと、Chromeなどのブラウザが自動的にブロックします。

よくあるエラー

Access to XMLHttpRequest at 'http://localhost:8000/api/user' from origin 'http://localhost:3000' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check

多くはLaravel側(APIサーバー側)のCORSやSanctumの適切な設定がされていないことで発生します。Cookieを扱う場合は Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin の整合が必須で、* は使えません。

CORSエラーを解決する手順(Sanctum/Cookie認証の完全版)

1. 追加パッケージは基本不要

Laravel 7以降はCORSサポートがフレームワークに組み込まれています(グローバルの HandleCors ミドルウェアが対応)。追加で barryvdh/laravel-cors を入れる必要は通常ありません。

※既存プロジェクトで config/cors.php が見当たらない場合は、プロジェクトのバージョンやスケルトンを確認し、必要に応じて設定ファイルを用意してください

2. config/cors.php の確認

Cookieベース認証(Sanctum)で最低限必要な例は次のとおりです。

config/cors.php
<?php  
return [ 
	'paths' => ['api/*', 'sanctum/csrf-cookie'], 
	'allowed_methods' => ['*'], 
	'allowed_origins' => [ 
		'http://localhost:3000', // フロントのオリジンを列挙(ポート違いは別オリジン)
		'http://127.0.0.1:3000',
    ], 
    'allowed_headers' => ['*'], 
    'exposed_headers' => [], 
    'max_age' => 0, 
    'supports_credentials' => true, // Cookieを使うなら必須
];

注意点

  • supports_credentialstrue のとき、allowed_origins* は使えない
  • フロントが複数のオリジン(例:localhost127.0.0.1)の場合は、すべて列挙するか、URLを統一する

3. Sanctumの設定(CookieベースのSPA認証)

config/sanctum.phpstateful に、Cookieで認証させたいオリジン(ポート込み) を指定します。

config/sanctum.php(抜粋)
<?php  
'stateful' => [ 
	'localhost:3000', 
	'127.0.0.1:3000',
],

.env で管理する場合は次のようにします。

.env
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
SESSION_DOMAIN=localhost

補足

  • ローカルでは SESSION_DOMAIN=localhost で十分なことが多い
  • サブドメイン間でCookieを共有する本番構成(app.example.comapi.example.com)では、SESSION_DOMAIN=.example.com のように先頭ドット付きで指定

4. ミドルウェアの確認

SanctumのSPA認証を利用するためのミドルウェアは、Laravelのバージョンによって設定箇所が異なります

  • Laravel 10以前:EnsureFrontendRequestsAreStateful が有効になっていることを確認

  • Laravel 11以降:bootstrap/app.php のミドルウェア設定で 「stateful API」相当を有効化(プロジェクトの雛形に従って設定)

api.php のルート例(認証が必要なAPI)

api.php
<?php
use  Illuminate\Http\Request; 
use  Illuminate\Support\Facades\Route; 

Route::middleware(['auth:sanctum'])->get('/user', function (Request $request) { 		
	return  $request->user();
});

5. Next.js側(Axios / fetch)の設定

Axiosを使う場合はCookie同送CSRFトークン連携を有効にします。

lib/api.ts
import axios from  'axios'; 

export  const api = axios.create({ 
	baseURL: 'http://localhost:8000', 
	withCredentials: true, // Cookieを送る  
	xsrfCookieName: 'XSRF-TOKEN', // Laravel Sanctumのデフォルト  
	xsrfHeaderName: 'X-XSRF-TOKEN',
}); 

// 最初にCSRFを初期化してからログイン  
await api.get('/sanctum/csrf-cookie'); 
await api.post('/login', { email, password }); 

// 以降は認証済みのままAPIを叩ける  
const me = await api.get('/api/user');

fetch を使う場合

lib/api.ts
await fetch('http://localhost:8000/sanctum/csrf-cookie', { 
	credentials: 'include',
}); 

await fetch('http://localhost:8000/login', { 
	method: 'POST', 
	credentials: 'include', 
	headers: { 
		'Content-Type': 'application/json', 
		'Accept': 'application/json' 
	}, 
	body: JSON.stringify({ email, password }),
}); 

const res = await fetch('http://localhost:8000/api/user', { 
	credentials: 'include', 
	headers: { 'Accept': 'application/json' },
});

ポイント

  • Axiosは withCredentials: truefetchcredentials: 'include' を必ず付ける

  • 最初に GET /sanctum/csrf-cookie を叩いてCSRFトークンをセットしてから、/login へPOSTする

本番環境での注意点

  • HTTPSは必須
    Secure 属性付きCookieはHTTPSでしか送れません。ローカルで動いても、本番でHTTPのままだとCookieが送られず認証に失敗します。

  • ドメイン設計
    SanсtumのSPA認証は同一トップレベルドメイン(サブドメイン可) を前提とします。app.example.comapi.example.com はOK、example.comexample.net のような別TLDはNGです(その場合はAPIトークン方式を使う)。

  • SameSiteとセキュリティ属性
    別TLDに跨るCookieが必要な設計をどうしても採る場合は SameSite=None; Secure が必要になりますが、Sanctumの想定外です。推奨構成は同一TLD + HTTPSです。

  • CORSを多層で設定しない
    アプリ(Laravel)・Webサーバ(Nginx/Apache)・CDNでCORSをそれぞれ設定すると競合しがちです。責任レイヤーを1箇所に絞るのが安全です。

  • 設定キャッシュ
    設定を変更したら php artisan optimize:clear などでキャッシュをクリアしましょう。

それでも直らない場合のチェックリスト

チェックポイント 確認内容
config/cors.php supports_credentials=true か。allowed_origins* を使っていないか。
CORSのパス pathssanctum/csrf-cookie を含めているか。
フロントのオリジン列挙 localhost127.0.0.1 など、実際に使う全てのオリジン(ポート込み)を列挙したか。
Axios / fetch Axiosは withCredentialsxsrfCookieName/xsrfHeaderName、fetchは credentials:'include' を付けたか。
Sanctum stateful にフロントのオリジン(ポート込み)を入れたか。.envSANCTUM_STATEFUL_DOMAINS は合っているか。
ミドルウェア 使用中のLaravelのバージョンに応じて、Sanctumのstateful設定が有効になっているか。
HTTPS 本番はHTTPSか。SESSION_SECURE_COOKIE=true(必要に応じて)になっているか。
プリフライト OPTIONS に対して200系で返しているか(Networkタブで確認)。
Cookie送信 NetworkタブでCookieが付与されているか、Set-Cookie がブロックされていないか。

まとめ

  • CORSは「異なるオリジンに対するアクセスをブラウザが制御する仕組み」。サーバ(Laravel)が正しいヘッダを返す必要がある

  • SanctumのCookie認証では、CORS設定 + statefulドメイン + CSRF初期化 + フロントの設定がすべて揃って初めて動く

  • 本番では同一トップレベルドメイン + HTTPSが基本。別TLDを跨ぐならCookieではなくAPIトークンを検討する

  • トラブル時はチェックリストに沿って、プリフライト、Cookie、設定キャッシュまで順に潰す

採用情報

アシストエンジニアリングでは一緒に働くフロントエンド、バックエンドエンジニアを募集しています!
少しでも興味のある方は、カジュアル面談からでもお気軽にお話ししましょう!

お問い合わせはこちらから↓
https://official.assisteng.co.jp/contact/

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?