授权码模式需web中间件保护/oauth/authorize路由、confidential客户端且redirect严格匹配;密码授权要求client_id/client_secret来自password_client=1记录,Laravel 11+已弃用;设备授权须启用enableDeviceGrant()并按interval轮询。

Passport 的 OAuth2 授权不是“开箱即用”的黑盒,它依赖你明确选择并正确配置授权类型——选错类型或漏配关键环节,401 或 invalid_grant 就会立刻出现,而不是等你上线才发现。
授权码模式(Authorization Code Grant)怎么配才不卡在 /oauth/authorize?
这是 Web 应用最常用也最容易出错的流程。核心问题不在代码,而在路由、中间件和客户端配置三者的对齐。
-
/oauth/authorize路由必须由web中间件组保护(不是api),否则 Laravel 无法读取 session 和 CSRF token,直接跳转失败或报CSRF token mismatch - 客户端必须是
confidential类型(即带client_secret),且redirect值要与数据库oauth_clients.redirect字段完全一致(包括协议、域名、端口、末尾斜杠) - 调用
Socialite或手动构造授权 URL 时,scope参数必须是空格分隔字符串(如read write),不能是数组或逗号分隔;若未定义 scope,默认只返回user作用域 - 用户点击“允许”后,回调地址收到的
code是一次性凭证,需立即用POST /oauth/token换取access_token,超时默认 10 分钟(由Passport::authorizationCodesExpireIn()控制)
密码授权(Password Grant)为什么总返回 invalid_client?
这不是密码错,而是客户端身份没被识别。Passport 默认生成的 “Laravel Password Grant Client” 只用于该模式,但必须满足三个硬性条件。
- 请求必须发往
/oauth/token,且grant_type=password,其他字段缺一不可:username、password、client_id、client_secret、scope(哪怕为空也要传) -
client_id和client_secret必须来自数据库中oauth_clients表里personal_access_client=0且password_client=1的那条记录(php artisan passport:install会自动创建) - 该客户端的
redirect字段可为空,但不能为null;若手动插入记录,务必设为''(空字符串),否则 Passport 会拒绝验证 - 注意:Laravel 11+ 已弃用密码授权,生产环境应优先迁移到 PKCE + 授权码模式
设备授权(Device Grant)轮询时一直返回 authorization_pending?
这表示用户还没完成确认,但设备端容易误判为服务异常。关键在于轮询逻辑和状态超时设置是否匹配。
- 首次调用
/oauth/device_authorization返回的interval字段(单位秒)是建议轮询间隔,不是固定值;实际应按此间隔 + 随机抖动(如 ±1s)发起请求,避免集群压垮 -
expires_in决定整个设备授权流程有效期(默认 15 分钟),用户必须在此时间内访问verification_uri并输入user_code,否则设备端最终会收到expired_token - 用户在
/oauth/approve-device页面点击“允许”后,令牌不会即时下发——设备需继续轮询,直到响应包含access_token和refresh_token字段为止 - 确保
Passport::enableDeviceGrant()在AuthServiceProvider@boot中调用,且php artisan passport:client --device创建的客户端类型为device(oauth_clients.grant_type=device_code)
真正难的不是写几行 Passport::routes(),而是理解每种授权类型背后的状态流转和安全约束——比如密码授权要求客户端保密,设备授权依赖异步轮询,授权码模式强依赖 session 完整性。漏掉任一环,错误就藏在 HTTP 状态码背后,而不是日志里。


















