You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 12 Sanctum对接React SPA时认证接口返回401未授权问题

Laravel 12 Sanctum对接React SPA调用认证接口返回401 Unauthorized问题

问题描述

在同顶级域名下使用Laravel 12的Sanctum组件对接React.js单页应用(SPA),登录、登出接口调用正常,XSRF-TOKEN已成功设置,但调用需认证的API接口时返回401未授权错误。

前端Axios配置

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配置

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配置

'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配置

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路由配置

<?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']);
    });
});

已尝试的解决措施

  • 确保Axios中设置了withCredentials: true
  • 验证登录前已调用/sanctum/csrf-cookie处理CSRF防护
  • 检查后续请求中已携带Authorization Bearer令牌
  • 确认前后端SESSION_DOMAIN和SANCTUM_STATEFUL_DOMAINS配置一致

进一步排查建议

  1. 验证令牌存储与携带正确性

    • 登录成功后打印localStorage.getItem('bricoloman_token'),确认令牌与后端登录接口返回的plainTextToken完全一致;
    • 在浏览器Network面板查看需认证接口的请求头,确认Authorization: Bearer [token]存在且令牌无拼写错误。
  2. 检查后端登录接口逻辑

    • 确保登录接口返回的是用户创建的Sanctum令牌:
      return response()->json([
          'token' => $user->createToken('auth_token')->plainTextToken,
          'user' => $user
      ]);
      
    • 避免返回数据库中存储的哈希令牌,必须使用plainTextToken。
  3. 调整SESSION_DOMAIN配置

    • 本地开发时尝试将SESSION_DOMAIN改为.localhost(注意前缀的点),确保跨端口的cookie共享生效;
    • 重启Laravel服务使配置生效。
  4. 查看Laravel日志

    • 打开storage/logs/laravel.log,查找401错误对应的详细日志,通常会提示令牌无效、用户未找到等具体原因。
  5. 临时放宽CORS配置测试

    • 将cors.php中的allowed_origins改为['*'],测试是否为跨域配置问题(生产环境请勿保留此设置)。
  6. 清除浏览器缓存与Cookie

    • 清除浏览器中localhost相关的所有Cookie和缓存,重新登录后再调用认证接口。

内容的提问来源于stack exchange,提问作者AZNIDI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:34:53