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

Laravel 11 Sanctum Cookie会话认证跨平台同步401问题排查

问题:Laravel+Vue平台与纯PHP平台同步认证后API请求返回401 Unauthorized

我有两个平台:一个基于纯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请求能正常通过认证。


问题原因分析

  1. 认证守卫不匹配:API路由使用auth:sanctum守卫,但Auth::loginUsingId()默认使用web守卫,Sanctum无法识别Web会话的登录状态。
  2. Session与Sanctum认证机制分离:Sanctum的API认证依赖自身令牌或配置后的会话认证,当前仅通过Web Guard完成登录,未生成Sanctum可识别的认证标识。
  3. 跨域/会话Cookie配置缺失:若两个平台域名不同,Cookie跨域传递可能存在问题,导致前端请求无法携带正确的Session/Sanctum认证信息。

解决方案

方案1:配置Sanctum支持Web会话认证(适合同域名/已配置跨域场景)

让Sanctum识别Web会话的登录状态,无需额外生成令牌:

  1. 修改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) : ''
))),
  1. 在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,
    ],
];
  1. 确保用户模型实现HasApiTokens接口:
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
    // ... 其他代码
}

方案2:生成Sanctum令牌并传递给前端(适合跨域/纯API场景)

验证JWT后生成Sanctum令牌,让前端存储并在请求时携带:

  1. 修改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);
}
  1. 前端获取令牌并在请求时携带:
// 假设后端在页面渲染时将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 10:14:55