passport 是 oauth2 服务端,需严格按序执行迁移与安装、显式启用路由、正确配置 guard 和请求头,否则必现 404/500/unauthorized;密码模式 username 必须为邮箱,密钥权限与 client 绑定逻辑也常被忽略。

Passport 不是“装完就能发 token”的登录组件,它本质是一个 OAuth2 服务端,必须先建表、生成密钥、配对路由、绑定 guard,漏掉任一环都会导致 404、500 或 Unauthorized —— 尤其是新手常卡在迁移没跑或 Passport::routes() 没启用对应子集。
php artisan migrate 和 passport:install 必须严格按顺序执行
Passport 的所有功能依赖数据库表:比如 oauth_clients 存客户端,oauth_access_tokens 存令牌。不先跑迁移,passport:install 虽然会成功输出 client_id/client_secret,但后续任何发 token 请求都会报 SQLSTATE[42S02]: Base table or view not found。
- 先确认
config/database.php中数据库连接可用,再执行:php artisan migrate
- 再运行:
php artisan passport:install
——它会生成oauth-private.key和oauth-public.key,并创建两条默认 client 记录(Personal Access Client 和 Password Grant Client) - 如果中途失败或想重来:
php artisan migrate:rollback --step=1回滚 Passport 迁移,清空migrations表中相关记录,再重跑 - 注意:
passport:install --force不会重建表,只覆盖密钥和 client;表结构仍需靠migrate
Passport::routes() 必须显式启用所需子路由
Passport 默认只注册部分路由,/oauth/token 和 /oauth/authorize 是分开管理的。只写 Passport::routes() 在 Laravel 10+ 里可能不生效,且 forPersonalAccessTokens() 已默认禁用。
- 在
app/Providers/AuthServiceProvider.php的boot()方法中,明确启用你需要的路由类型:Passport::routes(function ($router) { $router->forAccessTokens(); // 必须:密码模式 /token 端点 $router->forAuthorizationCodes(); // 授权码模式需要:/authorize 端点 $router->forPersonalAccessTokens(); // 测试用 createToken() 需要:/tokens 等 }); - 漏掉
forAccessTokens()→ 密码模式请求/oauth/token直接 404 - 漏掉
forAuthorizationCodes()→ 第三方网站跳转/oauth/authorize报 404 - 不启用
forPersonalAccessTokens()→$user->createToken()生成的 token 无法通过auth:api中间件校验
auth:api 中间件失效的三个高频原因
即使 token 正确,auth:api 仍返回 401 Unauthorized,大概率不是 token 本身问题,而是 guard 配置或请求头细节出错。
- 检查
config/auth.php中'api' => ['driver' => 'passport']是否设置 —— 不是token,也不是sanctum - 确保请求头为:
Authorization: Bearer {access_token},注意:Bearers 后有一个空格,多一个单引号、少一个空格、换行符混入都会导致解析失败 - 确认 User 模型同时满足两个条件:
use Illuminate\Foundation\Auth\User(即继承Authenticatable) +use Laravel\Passport\HasApiTokens;缺任一,$request->user()为 null - 别用
Auth::user()测试 API 路由 —— 它默认走webguard;必须用Auth::guard('api')->user()或带auth:api中间件的路由
密码模式 username 必须是邮箱,且不能靠模型配置绕过
Passport 内置的 GrantPasswordController 硬编码用 email 字段查用户,哪怕你把 User 模型的 $username 改成 phone,这里也不会自动适配。
- 换取 token 的 POST 请求体必须含:
{"grant_type":"password","client_id":"...","client_secret":"...","username":"user@example.com","password":"xxx"} - 若业务要求手机号登录,有两种解法:
– 改写App\Http\Controllers\LoginController,手动查User::where('phone', $request->username)->first(),再调用$user->createToken()
– 或在数据库层让email字段存手机号(不推荐,语义混乱) - 第三方客户端传错
username(如传了 ID 或 nickname),错误信息是"message": "These credentials do not match our records.",容易误判为密码错
最易被忽略的是密钥文件权限和 client_id 匹配逻辑:storage/oauth-private.key 必须被 Web 服务器用户(如 www-data)可读,否则签名失败;而 createToken() 生成的 token 总是绑定数据库里 personal_access_client = 1 的那条 client,不是 .env 里的 PASSPORT_PERSONAL_ACCESS_CLIENT_ID —— 这个值早已被忽略。











