Laravel 11 Sanctum Cookie会话认证跨平台同步401问题排查
我有两个平台:一个基于纯PHP实现会话认证,另一个采用Laravel 11 + Vue 3架构。纯PHP端已完成认证逻辑开发,现在需要同步两个平台的用户认证状态。目前通过JWT令牌将用户数据从纯PHP平台传递到Laravel平台,Laravel端验证令牌后完成用户认证并将数据存入session,但前端后续请求/user-data接口时收到401 Unauthorized错误。
相关代码
1. 纯PHP平台跳转函数
/** * Social Media App login request * @return void */ public function actionSocialMediaLogin(): void { $token = AuthService::buildJWT(SOCIAL_MEDIA_REDIRECT_URL); if ($token) { header('location:' . SOCIAL_MEDIA_REDIRECT_URL . '?token=' . $token); exit; } else { $this->render('dashboard/auth/error', [ 'error' => "Failed, refresh this page" ]); } }
2. Laravel验证令牌的Web路由
Route::get('/login-check', function () { return redirect('/'); })->middleware('validateToken');
3. ValidateToken中间件
public function handle(Request $request, Closure $next): Response { $token = $request->query('token'); if (!$token) { return redirect('login')->with('error', 'Token not provided'); } try { $decoded = JWT::decode($token, new Key(env('JWT_SECRET_KEY'), 'HS256')); // Store user data in session or auth context session(['userId' => $decoded->user_id, 'organizationId' => $decoded->organization_id, 'roleId' => $decoded->role_id]); Auth::loginUsingId($decoded->user_id); } catch (\Exception $e) { return redirect('login')->with('error', 'Invalid token'); } return $next($request); }
4. 前端获取用户数据的函数
const getUserData = async () => { await axios.get('/sanctum/csrf-cookie') return await axios .get("/user-data", { withCredentials: true, withXSRFToken: true }) .then((response) => { if (response.data.status == "success") { const newUser = response.data.user; const newOrganization = response.data.organization; const newRole = response.data.role; // Check if data is not empty if (!newUser || !newOrganization || !newRole) { return false; } // Save to pinia store user.value = newUser; organization.value = newOrganization; role.value = newRole; roleGroup.value = newRole.Name.split(" ")[0] .toLowerCase() .replace("_", " ") || ""; return true; } return false; }); };
5. 获取用户数据的API路由
Route::get("user-data", "getUserData")->middleware("auth:sanctum");
注:ValidateToken中间件运行正常,可正常获取session变量,但API请求后session状态丢失,期望完成首次认证后API请求能正常通过认证。
问题原因分析
- 认证守卫不匹配:API路由使用
auth:sanctum守卫,但Auth::loginUsingId()默认使用web守卫,Sanctum无法识别Web会话的登录状态。 - Session与Sanctum认证机制分离:Sanctum的API认证依赖自身令牌或配置后的会话认证,当前仅通过Web Guard完成登录,未生成Sanctum可识别的认证标识。
- 跨域/会话Cookie配置缺失:若两个平台域名不同,Cookie跨域传递可能存在问题,导致前端请求无法携带正确的Session/Sanctum认证信息。
解决方案
方案1:配置Sanctum支持Web会话认证(适合同域名/已配置跨域场景)
让Sanctum识别Web会话的登录状态,无需额外生成令牌:
- 修改
config/sanctum.php,确保stateful数组包含前端域名(同域名可忽略):
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))),
- 在
app/Http/Kernel.php的api中间件组中添加StartSession中间件,让API路由能读取Web会话:
protected $middlewareGroups = [ 'api' => [ \Illuminate\Session\Middleware\StartSession::class, // 添加此行 \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
- 确保用户模型实现
HasApiTokens接口:
use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; // ... 其他代码 }
方案2:生成Sanctum令牌并传递给前端(适合跨域/纯API场景)
验证JWT后生成Sanctum令牌,让前端存储并在请求时携带:
- 修改ValidateToken中间件,生成并存储Sanctum令牌:
public function handle(Request $request, Closure $next): Response { $token = $request->query('token'); if (!$token) { return redirect('login')->with('error', 'Token not provided'); } try { $decoded = JWT::decode($token, new Key(env('JWT_SECRET_KEY'), 'HS256')); $user = \App\Models\User::findOrFail($decoded->user_id); Auth::login($user); // 生成Sanctum令牌 $sanctumToken = $user->createToken('cross-platform-auth')->plainTextToken; // 将令牌存入session,前端可通过页面渲染获取 session(['sanctum_token' => $sanctumToken]); } catch (\Exception $e) { return redirect('login')->with('error', 'Invalid token'); } return $next($request); }
- 前端获取令牌并在请求时携带:
// 假设后端在页面渲染时将sanctum_token注入到window对象 const getUserData = async () => { let token = localStorage.getItem('sanctum_token'); if (!token && window.sanctumToken) { token = window.sanctumToken; localStorage.setItem('sanctum_token', token); } return await axios .get("/user-data", { headers: { 'Authorization': `Bearer ${token}` } }) .then((response) => { // 原有逻辑不变 }); };
方案3:API路由改用Web守卫认证(不推荐纯API场景)
若API与Web路由同属一个应用,可将API路由的认证中间件改为auth:web:
Route::get("user-data", "getUserData")->middleware("auth:web");
同时需配置跨域支持(若跨域),修改config/cors.php:
'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, // 必须设为true
内容的提问来源于stack exchange,提问作者Ahmed Alaa ElDin
相关产品推荐
相关产品推荐

