Laravel 6.x自定义API邮箱验证中间件无法获取$request->user('api')
问题分析与解决方案
首先得把Web和API守卫的核心差异掰明白,这是你疑惑的根源:
1. Web vs API 守卫的认证逻辑差异
- Web守卫:默认用
session驱动。用户登录成功后,Laravel会把用户信息存在服务器端的session里,同时给浏览器种一个带session ID的cookie。后续请求只要带着这个cookie,Laravel就能通过session ID找到对应的用户,所以$request->user()能直接拿到用户实例——这也是Web版EnsureEmailIsVerified中间件能正常工作的原因:它是加在登录后的路由(比如后台首页)上的,这时候用户已经通过session完成认证了。 - API守卫:默认用
token驱动(你用了Passport就是Bearer Token)。它没有session和cookie的概念,完全依赖请求头里的Authorization: Bearer {token}来识别用户。而登录请求本身是没有Token的——你总不能让用户还没登录就先给你传Token吧?所以这时候$request->user('api')返回null是完全正常的,因为认证流程还没走完,Laravel根本不知道你是谁。
2. 你的核心误区:把中间件加错了地方
你应该是把自定义的EnsureApiEmailIsVerified中间件直接加到了登录路由上,但登录请求本身是「未认证状态」,中间件在路由处理前执行,这时候用户还没被系统识别,自然拿不到用户实例。
而Web版的EnsureEmailIsVerified从来不会加在登录路由上,它是用来保护那些「已经登录后才能访问」的页面,这时候session已经存在,所以能拿到用户。
3. 适配你业务场景的正确实现思路
你的需求是:批量导入/工厂用户首次登录需改密、邮箱验证后才能正常使用。正确的流程应该把判断逻辑放在登录成功后的步骤里,而不是依赖前置中间件:
步骤一:调整登录控制器的逻辑
修改你的API登录控制器(比如ApiLoginController),在验证用户凭证后,直接做邮箱验证和强制改密的判断:
public function login(Request $request) { // 验证登录凭证 $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required' ]); // 用api守卫尝试认证 if (!Auth::guard('api')->attempt($credentials)) { return response()->json(['message' => '邮箱或密码错误'], 401); } // 拿到认证后的用户实例 $user = Auth::guard('api')->user(); // 1. 检查邮箱是否已验证 if (is_null($user->email_verified_at)) { return response()->json([ 'message' => '请先点击邮箱中的验证链接完成验证' ], 403); } // 2. 检查是否需要强制改密(可以给users表加个`force_password_change`字段,导入时设为true) if ($user->force_password_change) { return response()->json([ 'message' => '请修改初始密码后登录', 'need_change_password' => true ], 403); } // 3. 生成Passport Token返回给前端 $token = $user->createToken('API Access Token')->accessToken; return response()->json([ 'token' => $token, 'user' => $user ]); }
步骤二:自定义中间件的正确用法
如果要保护登录后的API路由(比如用户个人中心、数据接口),这时候再把EnsureApiEmailIsVerified中间件加上,这时候用户已经携带了有效的Bearer Token,$request->user('api')就能正常拿到用户:
class EnsureApiEmailIsVerified { public function handle($request, Closure $next) { $user = $request->user('api'); // 用户未登录或邮箱未验证,直接返回403 if (!$user || is_null($user->email_verified_at)) { return response()->json([ 'message' => '请先完成邮箱验证' ], 403); } return $next($request); } }
然后在路由中这样使用:
Route::middleware(['auth:api', 'ensure-api-email-verified'])->group(function () { Route::get('/user/profile', 'UserController@getProfile'); Route::put('/user/password', 'UserController@updatePassword'); // 其他需要登录+验证邮箱才能访问的接口 });
步骤三:跨域配置注意事项
因为前端是不同域名的Vue,要确保Laravel正确配置了CORS:
- 可以使用
barryvdh/laravel-cors包,在config/cors.php里允许前端域名的请求,同时开启supports_credentials,并允许Authorization请求头(Bearer Token需要这个)。
最后再总结下
你之前的问题本质是混淆了「登录前」和「登录后」的认证状态:Web中间件是在登录后用的,API的登录请求本身没有Token,自然拿不到用户。把判断逻辑移到登录成功后的代码里,就能解决你的问题了。
内容的提问来源于stack exchange,提问作者Barry van Biljon
相关产品推荐
相关产品推荐

