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

Laravel Sanctum通用路由实现:支持登录/未登录用户数据区分

问题描述

我使用Laravel+React+Sanctum开发应用,需要设置通用路由,允许登录与未登录用户访问,并根据用户认证状态展示不同内容。目前遇到的问题是:只有在路由上添加auth::sanctum中间件时,才能通过Auth::check()判断用户是否登录,或通过Auth::user()获取当前登录用户信息。我尝试过$request->user('sanctum'),还自定义了中间件,但这些方案都无效。想在控制器方法中判断用户登录状态,并在用户登录时获取用户数据,请问该如何实现?现有方案为何无法生效?

补充信息

  1. 不使用任何中间件时,Auth::user()返回null,Auth::check()返回false。
  2. 采用HttpOnly Cookie实现登录,后端登录代码如下:
public function login(Request $request)
{
    if (!Auth::attempt($request->only('email', 'password'))) {
        return response([
            'error' => 'Invalid Credentials'
        ], Response::HTTP_UNAUTHORIZED);
    }

    $user = Auth::user();

    $token = $user->createToken('token')->plainTextToken;

    $cookie = cookie('jwt', $token, 60 * 24);
    return response([
        'username' => $user->username
    ])->withCookie($cookie);
}
  1. React端相关配置与代码:
  • index.js中axios配置:
axios.defaults.baseURL = "http://localhost/api/";
axios.defaults.withCredentials = true;
  • login.jsx登录逻辑:
const submit = async (value) => {
    setInProgress(true);
    await axios
      .post("login", {
        email: value.email,
        password: value.password,
      })
      .then((data) => {
        const dataJSON = data;
        setInProgress(false);

        UserStore.storeUser({
          username: dataJSON.data.username,
          avatar: dataJSON.data.avatar,
        });
        setRedirect(true);
      })
      .catch((err) => {
        setTimeout(() => {
          setInProgress(false);
          setCantLogin(true);
        }, 200);
      });
  };
  • App.jsx初始化逻辑:
useEffect(() => {
    i18n.changeLanguage("pl");
    axios.get("/user").then((data) => {
      const dataJSON = data;
      UserStore.storeUser({
        username: dataJSON.data.username,
        avatar: dataJSON.data.avatar ? dataJSON.data.avatar : null,
      });
    });
  });
  1. 请求http://localhost/sanctum/csrf-cookie时出现循环,页面无法正常工作,是否所有页面都需要执行该请求?
  2. 登录后已请求该端点,XSRF Cookie已存在,但问题仍未解决,更新后的登录代码如下:
const submit = async (value) => {
    setInProgress(true);
    await axios
      .post("login", {
        email: value.email,
        password: value.password,
      })
      .then((data) => {
        const dataJSON = data;
        setInProgress(false);

        UserStore.storeUser({
          username: dataJSON.data.username,
          avatar: dataJSON.data.avatar,
        });
        const url = "/sanctum/csrf-cookie";
        fetch(url).then(() => {
          alert("udało się");
        });
        setRedirect(true);
      })
      .catch((err) => {
        setTimeout(() => {
          setInProgress(false);
          setCantLogin(true);
        }, 200);
      });
  };
解决方案

现有方案无效原因

  1. 自定义中间件逻辑错误:你的自定义中间件仅检查$request->bearerToken(),但你用HttpOnly Cookie存储token,前端并未将token放在请求头的Authorization字段中,导致$request->bearerToken()无法获取到值,自然无法设置登录用户。
  2. $request->user('sanctum')未生效:未启用Sanctum的Cookie认证中间件,Sanctum无法自动从Cookie中读取token并解析用户信息。
  3. CSRF请求时机错误:你在登录成功后才请求sanctum/csrf-cookie,但CSRF Cookie需要在发起第一个POST请求(如登录)前获取,否则登录请求会因CSRF校验失败被拦截;且无需所有页面都请求该端点,仅需在首次发起有状态请求前调用一次即可。

正确实现步骤

1. 配置Sanctum Cookie认证中间件

在app/Http/Kernel.php的api中间件组中添加Sanctum的状态请求中间件,该中间件会自动处理Cookie中的Sanctum token:

protected $middlewareGroups = [
    'api' => [
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

2. 实现宽松的用户状态判断

方法一:控制器内直接判断

无需给路由添加强制认证中间件,直接在控制器方法中通过Sanctum guard获取用户:

public function index(Request $request)
{
    // 通过sanctum guard尝试获取用户,不会强制拦截未登录请求
    $user = Auth::guard('sanctum')->user();
    
    if ($user) {
        // 已登录逻辑:返回用户信息或对应内容
        return response()->json([
            'authenticated' => true,
            'user' => $user->only('id', 'username', 'avatar')
        ]);
    } else {
        // 未登录逻辑:返回公共内容
        return response()->json([
            'authenticated' => false,
            'content' => '公共内容'
        ]);
    }
}

方法二:自定义宽松认证中间件

若多个控制器需要此逻辑,可编写中间件自动绑定Sanctum用户到默认Auth:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthenticateSanctumOptional
{
    public function handle(Request $request, Closure $next)
    {
        // 尝试用sanctum guard认证用户,不强制拦截未登录请求
        if ($user = Auth::guard('sanctum')->user()) {
            Auth::setUser($user);
        }

        return $next($request);
    }
}

在Kernel.php中注册该中间件:

protected $routeMiddleware = [
    // ...其他中间件
    'auth.sanctum.optional' => \App\Http\Middleware\AuthenticateSanctumOptional::class,
];

给需要的路由添加该中间件:

Route::get('/user', [UserController::class, 'show'])->middleware('auth.sanctum.optional');

之后即可在控制器中直接使用Auth::check()和Auth::user()判断用户状态。

3. 修复前端CSRF请求逻辑

将sanctum/csrf-cookie请求放在登录请求之前:

const submit = async (value) => {
    setInProgress(true);
    try {
        // 先获取CSRF Cookie
        await axios.get('/sanctum/csrf-cookie');
        // 再发起登录请求
        const data = await axios.post("login", {
            email: value.email,
            password: value.password,
        });
        
        UserStore.storeUser({
            username: data.data.username,
            avatar: data.data.avatar,
        });
        setInProgress(false);
        setRedirect(true);
    } catch (err) {
        setTimeout(() => {
            setInProgress(false);
            setCantLogin(true);
        }, 200);
    }
};

无需在所有页面调用sanctum/csrf-cookie,浏览器会自动保存CSRF Cookie,后续请求会自动携带。

4. 检查Laravel环境配置

确保.env文件中相关配置正确:

SESSION_DRIVER=cookie
SANCTUM_STATEFUL_DOMAINS=localhost:3000,localhost # 对应React端口和后端域名
SESSION_DOMAIN=localhost # 前后端同域名时设置为localhost

内容的提问来源于stack exchange,提问作者Sdsdsd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 15:17:05