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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 08:57:31