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

Laravel Passport PKCE模式下注册后重定向SPA并授权的实现问题

问题背景与需求
  • 架构:认证/资源服务器与单页应用(SPA)部署在不同容器、服务器上,采用Proof Key for Code Exchange(PKCE)公开客户端
  • 登录流程:遵循PKCE规范,运行正常
  • 注册流程:用户在SPA选择定价套餐后,携带套餐、权限范围参数跳转到认证服务器完成注册与支付,需重定向回SPA仪表盘并携带有效访问凭证,实现用户直接使用应用
  • 核心问题:PKCE公开客户端无法通过HasApiToken的createToken方法生成令牌(无个人/私有客户端),不想通过服务端内部调用OAuth端点(非最佳实践),需合规实现跨域认证与注册后的凭证传递
可行解决方案

方案1:复用PKCE授权码流程(推荐)

利用已有的PKCE授权码机制,在注册完成后直接生成授权码,让SPA按正常流程换取令牌:

  1. 在认证服务器的注册完成逻辑中,生成PKCE兼容的授权码:
    use Laravel\Passport\Client;
    use Laravel\Passport\Code;
    use Illuminate\Support\Str;
    
    // 获取对应SPA的PKCE公开客户端
    $spaClient = Client::where('name', '你的SPA客户端名称')
        ->where('personal_access_client', false)
        ->first();
    
    // 生成关联当前用户的授权码
    $authCode = Code::create([
        'user_id' => $user->id,
        'client_id' => $spaClient->id,
        'scopes' => request()->get('scopes', []), // 注册时携带的权限范围
        'redirect_uri' => $spaClient->redirect,
        'code' => Str::random(40),
        'expires_at' => now()->addMinutes(5),
    ]);
    
  2. 重定向回SPA,携带授权码与state参数(保持PKCE流程一致性):
    return redirect()->to(sprintf(
        '%s?code=%s&state=%s',
        $spaClient->redirect,
        $authCode->code,
        request()->get('state', Str::random(20))
    ));
    
  3. SPA收到授权码后,用之前生成的code_verifier向认证服务器请求access_token,完成正常PKCE流程

方案2:创建专用内部客户端(多应用SSO场景适配)

为SSO体系创建一个仅用于注册回调的私有客户端,合规生成令牌:

  1. 生成私有客户端:
    php artisan passport:client --private
    
    记录客户端ID与密钥,存入环境变量
  2. 在注册控制器中,通过该客户端为用户生成令牌:
    use Laravel\Passport\Client;
    use Illuminate\Support\Str;
    
    $internalClient = Client::find(env('REGISTRATION_CALLBACK_CLIENT_ID'));
    $plainTextToken = Str::random(80);
    
    // 直接生成关联客户端的令牌
    $user->tokens()->create([
        'name' => 'Registration Session',
        'token' => hash('sha256', $plainTextToken),
        'abilities' => request()->get('scopes'),
        'client_id' => $internalClient->id,
        'expires_at' => now()->addDays(1),
    ]);
    
  3. 重定向回SPA时,将令牌放在URL哈希片段中传递(避免日志泄露):
    return redirect()->to(sprintf(
        '%s#access_token=%s&token_type=Bearer&expires_in=86400',
        $spaClient->redirect,
        $plainTextToken
    ));
    
    SPA通过window.location.hash提取令牌,存入localStorage或sessionStorage

方案3:一次性临时凭证交换机制

生成一次性临时码,让SPA主动交换令牌,避免直接传递敏感凭证:

  1. 注册完成后生成临时码并缓存(设置短过期时间):
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Str;
    
    $tempCode = Str::random(60);
    // 5分钟过期,绑定用户ID
    Cache::put('reg_temp_code:' . $tempCode, $user->id, 300);
    
  2. 重定向回SPA携带临时码:
    return redirect()->to(sprintf(
        '%s?temp_code=%s',
        $spaClient->redirect,
        $tempCode
    ));
    
  3. SPA向认证服务器的专用端点请求交换令牌:
    // SPA端示例(axios)
    const tempCode = new URLSearchParams(window.location.search).get('temp_code');
    axios.post('/api/reg/exchange-token', {
        temp_code: tempCode,
        client_id: '你的SPA客户端ID',
        scopes: ['read', 'write']
    }).then(res => {
        localStorage.setItem('access_token', res.data.access_token);
        // 跳转至仪表盘
    });
    
  4. 认证服务器验证临时码并返回令牌:
    public function exchangeToken(Request $request)
    {
        $userId = Cache::pull('reg_temp_code:' . $request->temp_code);
        if (!$userId) {
            return response()->json(['error' => '无效或过期的临时码'], 400);
        }
    
        $user = User::findOrFail($userId);
        $client = Client::findOrFail($request->client_id);
        
        // 生成PKCE兼容的令牌
        $token = $user->createToken('SPA Session', $request->scopes, $client)->accessToken;
        return response()->json([
            'access_token' => $token,
            'token_type' => 'Bearer',
            'expires_in' => 86400
        ]);
    }
    
关键安全注意事项
  • 禁止在URL查询参数中传递access_token:会被浏览器历史、服务器日志记录,存在泄露风险,优先用哈希片段或API交换
  • 所有通信必须使用HTTPS,避免明文传输敏感数据
  • 多应用SSO场景下,严格校验客户端的redirect_uri,防止恶意跳转
  • PKCE流程中,code_verifier必须由SPA生成并存储,即使服务端生成授权码,后续令牌交换仍需验证该值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 12:03:08