结论是用sanctum而非passport,除非需oauth2;auth:sanctum中间件按顺序验证session+csrf或数据库token,不可拆分;createtoken返回对象须取plaintexttoken;登出需主动revoke,非仅删前端token。

直接说结论:用 Sanctum,别碰 Passport 除非你真需要 OAuth2 流程;token 不是“生成就完事”,关键在怎么传、怎么验、怎么失效。
auth:sanctum 中间件到底认什么?
它不是只认 Authorization: Bearer xxx,也不是只认 cookie —— 它按固定顺序尝试两种方式:
先查 laravel_session + XSRF-TOKEN cookie(针对 SPA),再 fallback 到数据库查 personal_access_tokens 表里的 token(针对移动端或纯 API)。
这个顺序不可改,也不能拆开用。
- 如果你的前端是 Vue/React(跑在
http://localhost:5173),且没配SANCTUM_STATEFUL_DOMAINS,它会跳过 session 分支,直接走 token 分支 → 此时必须带Authorization头,否则 401 - 如果你的请求来自 Postman 或 curl,默认不发 cookie,也必然走 token 分支
- 中间件名必须写全
auth:sanctum,不能只写auth或sanctum,否则 Laravel 找不到守卫
createToken() 返回的是对象,不是字符串
常见错误:登录成功后直接 return $user->createToken('api');,结果前端拿到空值或报错 Object of class Laravel\Sanctum\NewAccessToken could not be converted to string。
- 必须显式取
plainTextToken属性:$user->createToken('mobile')->plainTextToken - 这个明文只出现一次,返回后立刻消失;数据库存的是哈希值,无法反查
- 不要用
$user->tokens()->create()手动插入,会绕过 Sanctum 的作用域、哈希和时间戳逻辑 - 如果要限制 token 权限,传第二个参数数组:
$user->createToken('read-only', ['read'])
SPA 前端调用 /login 一直 419?先看 CSRF
这不是认证失败,是 CSRF 校验拦在了 Auth::attempt() 之前。Sanctum 对 SPA 默认强制走 session + CSRF 流程,不走纯 token。
- 必须先 GET 一次
/sanctum/csrf-cookie,让服务端 setXSRF-TOKENcookie - 该接口不能加
auth:sanctum中间件(否则死循环) - 前端 axios 要设
axios.defaults.withCredentials = true;fetch 要加credentials: 'include' -
config/cors.php中'supports_credentials' => true,且'allowed_origins'必须写具体域名(如http://localhost:5173),不能是*
token 怎么安全登出和清理?
前端删 localStorage 里的 token 没用,后端 token 还活着。登出必须主动 revoke。
- 登出当前设备:
$request->user()->currentAccessToken()->delete() - 登出其他所有设备(保留当前):
$request->user()->tokens()->where('id', '!=', $request->user()->currentAccessToken()->id)->delete() - 彻底清空:
$request->user()->tokens()->delete() - 别依赖过期时间自动清理 ——
personal_access_tokens表默认没有expires_at字段,得自己加迁移并重写createToken逻辑
最常被忽略的一点:Sanctum 的 token 认证和 session 认证共用同一个中间件,但触发路径完全不同。你看到的 401 和 419,背后可能是完全不同的协议层问题,别急着改认证逻辑,先确认请求到底走的是哪条路。











