I am using Laravel Sanctum with a React.js SPA, both residing on the same top-level domain. However, after a successful login, API calls to authenticated endpoints return a 401 Unauthorized error.(laravel 12)
import axios from 'axios';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api/v1';
const VITE_TOKEN_STORAGE_KEY = import.meta.env.VITE_TOKEN_STORAGE_KEY || 'bricoloman_token';
const SANCTUM_BASE_URL = import.meta.env.SANCTUM_BASE_URL || 'http://localhost:8000';
// Base configuration for Axios instance
const api = axios.create({
baseURL: API_BASE_URL,
withCredentials: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
const sanctumApi = axios.create({
baseURL: SANCTUM_BASE_URL,
withCredentials: true,
});
api.interceptors.request.use(config => {
const token = localStorage.getItem(VITE_TOKEN_STORAGE_KEY);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
response => response,
error => {
return Promise.reject(error);
}
);
export const getCsrf = async () => {
await sanctumApi.get('/sanctum/csrf-cookie');
};
.env Configuration
SANCTUM_STATEFUL_DOMAINS=localhost:3000
SESSION_DOMAIN=localhost
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
APP_URL=http://localhost
sanctum.php Configuration
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:8000,localhost:8080,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '',
Sanctum::currentApplicationUrlWithPort()
))),
cors.php Configuration
return [
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout', 'register'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000', env('APP_URL')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => ['Authorization'],
'max_age' => 0,
'supports_credentials' => true,
];
api.php Route Configuration
<?php
use App\Http\Controllers\Api\V1\AuthController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::prefix('v1')->group(function () {
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::post('/logout', [AuthController::class, 'logout']);
});
});
Issue The login and logout endpoints work fine when called from the React.js SPA using Axios. The XSRF-TOKEN is successfully set. However, when calling authenticated API endpoints, I get a 401 Unauthorized error. What I Have Tried Ensured withCredentials: true is set in Axios. Verified that CSRF protection is correctly handled by calling /sanctum/csrf-cookie before login. Checked that the Authorization Bearer token is set in subsequent requests. Confirmed that SESSION_DOMAIN and SANCTUM_STATEFUL_DOMAINS match between frontend and backend.