Laravel中Axios Post请求的CSRF令牌不匹配



我正在做一个项目,使用React前端和Laravel后端。我正在尝试设置我的认证系统。我使用Sanctum使用SPA认证。我成功地利用了sanctum/csrf-cookie路由,其中给出了XSRF-Token cookie。当我随后尝试登录时,我得到一个419错误,CSRF令牌不匹配。不存在XSRF-Token。有趣的是,如果我执行get请求,就像下面的"测试"路由一样,XSRF cookie就会出现。但是,当我执行post请求时,如在向登录路由发布时,cookie不存在,并且我得到一个419错误。

我正在本地运行这个。前端运行在localhost:3000,后端运行在localhost:8888。以下是各种相关的代码段。

LoginForm.js

let data = {
  email: e.target[0].value,
  password: e.target[1].value
}
axios.get('http://localhost:8888/sanctum/csrf-cookie')
.then((res) => {
  axios.post('http://localhost:8888/login', data)
  .then((res) => {
    axios.get('http://localhost:8888/user')
  })
})

Kernel.php

protected $middleware = [
        AppHttpMiddlewareTrustProxies::class,
        FruitcakeCorsHandleCors::class,
        AppHttpMiddlewarePreventRequestsDuringMaintenance::class,
        IlluminateFoundationHttpMiddlewareValidatePostSize::class,
        AppHttpMiddlewareTrimStrings::class,
        IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class,
        IlluminateSessionMiddlewareStartSession::class,
        IlluminateSessionMiddlewareAuthenticateSession::class,
        IlluminateViewMiddlewareShareErrorsFromSession::class,
    ];
    protected $middlewareGroups = [
        'web' => [
            AppHttpMiddlewareEncryptCookies::class,
            IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
            IlluminateViewMiddlewareShareErrorsFromSession::class,
            AppHttpMiddlewareVerifyCsrfToken::class,
            IlluminateRoutingMiddlewareSubstituteBindings::class,
            AppHttpMiddlewareHandleInertiaRequests::class,
        ],
        'api' => [
            LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            IlluminateRoutingMiddlewareSubstituteBindings::class,
        ],
    ];
    protected $routeMiddleware = [
        'auth' => AppHttpMiddlewareAuthenticate::class,
        'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
        'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,
        'can' => IlluminateAuthMiddlewareAuthorize::class,
        'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
        'password.confirm' => IlluminateAuthMiddlewareRequirePassword::class,
        'signed' => IlluminateRoutingMiddlewareValidateSignature::class,
        'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
        'verified' => IlluminateAuthMiddlewareEnsureEmailIsVerified::class,
    ];

.env

SESSION_DRIVER=cookie
CLIENT_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=http://localhost:3000

Bootstrap.js

axios = require('axios');
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;

Web.php

Route::get('/testing', function () {
    return "Testing.";
});
Route::post('/login', function(Request $request) {
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required'],
    ]);
    if (Auth::attempt($credentials)) {
        $request->session()->regenerate();
        $id = Auth::id();
        $user = User::find($id);
        return $user;
    }
    return back()->withErrors([
        'email' => 'The provided credentials do not match our records.',
    ]);
});

Sanctum.php

'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,localhost:8888,
    Sanctum::currentApplicationUrlWithPort()
))),

Cors.php

'paths' => [
        'api/*',
        'sanctum/csrf-cookie',
        'login',
        'logout',
        'register',
        'user/password',
        'forgot-password',
        'reset-password',
        'user/profile-information',
        'email/verification-notification',
        'testing',
        'user',
        'checkAuth'
    ],
'allowed_methods' => ['*'],
'allowed_origins' => [env('CLIENT_URL')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,

让我们逐一回顾使用sanctum应用SPA认证所需的内容。

  • 首先,它们必须共享相同的顶级域,但是每个都可以托管在子域上,如果你使用本地主机,那么这应该是好的。
  • 接下来,您应该配置您的SPA将从哪个域发出请求。 SANCTUM_STATEFUL_DOMAINS . 如果通过包含端口(127.0.0.1:8000)的URL访问应用程序,则应确保在域中包含端口号。这里,您似乎错过了端口。
  • 接下来,您应该添加中间件,如https://laravel.com/docs/9.x/sanctum#sanctum-middleware中所提到的,这似乎已经完成了。
  • 下一步修复cors问题:首先,您需要在config/cors.php上将supports_credentials设置为true。接下来,您应该在应用程序的全局axios实例上启用withCredentials选项。axios.defaults.withCredentials = true;
  • 最后,您应该确保应用程序的会话cookie域配置支持根域的任何子域。SESSION_DOMAIN,看起来你已经在这里设置了localhost。

总的来说,似乎你需要修复的东西:

  • 如果您从端口访问,则需要添加端口。
  • 你需要在config/cors.php上设置supports_credentials为true

有关详细信息,请参见:https://laravel.com/docs/9.x/sanctum#spa-authentication

根据Laravel Sanctum文档:

另外,你应该确保你发送了Accept: application/json头与你的请求。

因为我们在你的设置中找不到错误的配置,我建议在你的api组中添加一个自定义中间件,它会自动添加application/json头:

/* app/Http/Middleware/AjaxHeader.php */
namespace AppHttpMiddleware;
use Closure;
class AjaxHeader
{
    /**
     * Handle incoming request
     *
     * @param IlluminateHttpRequest $request
     * @param Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $request->headers->add([
            'accept' => 'application/json',
        ]);
        return $next($request);
    }
}

并将其添加到您的'api' middlewareGroups:

/* app/Htpp/Kernel.php */
// ...
protected $middlewareGroups = [
   // ...
   'api' => [         
      LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful::class,
      'throttle:api',
      IlluminateRoutingMiddlewareSubstituteBindings::class,
      AppHttpMiddlewareAjaxHeader::class,
   ],
];
// ...

我知道它很老了,但我找到的唯一解决方案(在这里发布了一个单独的问题)是POST/PUT等。请求时,我必须通过sanctum (get(sanctum/csrf-cookie))获得一个新的令牌,然后它才能工作。不知道为什么GET不需要它,为什么POST/PUT/PATCH等…。

最新更新