如何使用 Laravel 和 Nuxtjs 进行身份验证,包括登录、注销和密码重置?

tim*_*ois 3 laravel vue.js nuxt.js laravel-8

我找不到任何资源来帮助解决这个问题,有一些 repos 提供了某种类型的基础,但实际上并没有很多。

目标:运行 Laravel 具有后端 API,运行 NuxtJS 作为前端 SPA,两个独立的位置或合并为一个。

需要在两个系统之间进行适当的身份验证才能登录用户。Laravel Sanctum试图解决一些 SPA 问题,但很难找到真正显示完整设置示例的适当文档。

  1. 测试了这个想法https://github.com/zondycz/laravel-nuxt-sanctum但失败了,不适用于 npm,必须使用 yarn,但是,登录错误,不能开箱即用。回购需要工作。

  2. 本教程使用 Laravel 作为后端在 Nuxt SPA 中进行安全身份验证非常深入,看起来很有希望,但是,刷新令牌似乎不适用于 SPA 端,因为作者在静态 nuxt 上开发了它。虽然我觉得它可以修改工作,但我还没有找到解决方案。

  3. 这个模板,Laranuxt看起来很有前途,虽然我还没有尝试过,我不确定他们此时是否会定期更新它,它之前是由Laravel Nuxt JS构建的(废弃项目)

我能够运行 #2,虽然刷新令牌不能正常工作,但我仍然可以对用户进行身份验证,但现在另一个问题是密码重置,我无法通过 nuxt 表单正确设置。

有没有人通过这些框架之间的通信找到资源或解决了这个问题?还是我要完成一个似乎看不到尽头的兔子洞?

我想另一种说法是,你能做一个完全安静的身份验证系统吗?

希望这不是一个太宽泛的话题,在这个问题上寻找一些指导,因为如果不自己编写太多核心代码,很难找到合适的教程或文档。

Nic*_*wes 9

在将前端和后端拆分到不同的域时,似乎很多人都难以为 SPA 身份验证实施 Sanctum,问题通常与 CORS 相关。Sanctum 文档很棒,但假设您了解 CORS(或假设请求是同源的)。我会按照我的想法分解设置,在我觉得文档不足的地方提供一点额外的支持。一个很长的答案,但最后我解决您的问题,该问题似乎专门针对身份验证。

取自 Sanctum 文档:

首先,您应该配置您的 SPA 将从哪些发出请求。

假设您的前端应用程序位于https://www.my-awesome-app.io域是什么?http://localhost:3000 呢?域映射到 IP 地址,而不是协议或端口号。因此,给定示例中的域将是www.my-awesome-app.iolocalhost。考虑到这一点,在此阶段您需要做的就是转到sanctum.phpconfig 目录中的文件并设置'stateful'键的值以匹配 Laravel API 将从其接收请求的域。尽管域名根据定义不包括端口号,但 Sanctum 文档非常清楚地表明,如果您通过需要特定端口的 URL 访问,这也是必需的。

/config/sanctum.php
...
'stateful' => [
    'localhost:3000',
],
Run Code Online (Sandbox Code Playgroud)

或者

'stateful' => [
    'my-awesome-app.io',
],
Run Code Online (Sandbox Code Playgroud)

.env 文件在这里很有用。

如果您无法通过在单独子域上执行的 SPA 对您的应用程序进行身份验证,则您可能错误配置了 CORS(跨源资源共享)或会话 cookie 设置。

的确。那么正确的设置是什么样的呢?假设最近的 Laravel 版本使用了 Fruitcake/laravel-cors 包,你的 /config 文件夹中将有一个 cors.php 文件。默认看起来像:

默认

/config/cors.php
...
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
Run Code Online (Sandbox Code Playgroud)

我们在这里有一些工作要做。首先,paths. 目前,我们的 Laravel API 设置为仅允许来自任何外部来源的请求尝试访问 /api/ routes *. 当我们按照 Sanctum 文档的要求尝试从 path 访问 csrf cookie 时,这可能会导致早期出现问题/sanctum/csrf-cookie。我们的 cors.php 文件中没有明确允许对此路径的请求,因此它们将失败。要修复,我们可以这样做:

'paths' => [
    'api/*',
    'sanctum/csrf-cookie'
]
Run Code Online (Sandbox Code Playgroud)

现在将允许对 /sanctum/csrf-cookie 的请求。一点题外话,我觉得个人非常有用的前缀从改变sanctumapi,这样我可以设置一个基准URL我的HTTP客户端(通常是爱可信)。

import axios from 'axios';

axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'http://localhost:3000/api';
Run Code Online (Sandbox Code Playgroud)

要更改路径,您可以在 /config/sanctum.php 文件中更改以下内容:

'prefix' => 'api',
Run Code Online (Sandbox Code Playgroud)

现在对 /api/csrf-cookie 的 GET 请求将返回 cookie,而不是 /sanctum/csrf-cookie。

接下来,allowed-origins. 默认情况下,它设置为*,这意味着“任何来源”。源是向 Laravel API 发送请求的应用程序的协议、域和端口号。所以回到我们之前的例子,它们的起源是http://localhost:3000and https://www.my-awesome-app.io。这些是您应该用于允许来自前端应用程序的请求的确切值:

'allowed_origins' => ['http://localhost:3000'],
Run Code Online (Sandbox Code Playgroud)

我建议将其移至 .env 文件,并为本地和生产设置单独的来源。

/config/cors.php
...
'allowed_origins' => [env('ALLOWED_ORIGINS')],


/.env
...
ALLOWED_ORIGINS=http://localhost:3000
Run Code Online (Sandbox Code Playgroud)

文档确实提到了我们 cors 配置的最后一部分,即

'supports_credentials' => false,
Run Code Online (Sandbox Code Playgroud)

必须改为:

'supports_credentials' => true,
Run Code Online (Sandbox Code Playgroud)

我们的 /config/cors.php 文件现在看起来像:

修改的

/config/cors.php
...
'paths' => [
    'api/*',
    'sanctum/csrf-cookie'
],
'allowed_methods' => ['*'],
'allowed_origins' => [env('ALLOWED_ORIGINS')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
Run Code Online (Sandbox Code Playgroud)

奖励信息,Chrome 将不允许向返回标头的服务器发出凭据请求 Access-Control-Allow-Origin: *

Google Chrome:当凭据标志为真时,不能在“Access-Control-Allow-Origin”标头中使用通配符“*”

所以你应该确保你在你的 cors 配置中设置了一个特定的来源!

最后,您应该确保应用程序的会话 cookie 域配置支持根域的任何子域。您可以通过为域添加前导 . 在您的会话配置文件中:

这并不复杂,但似乎它可以抓住人们,所以我想我会提到它。鉴于我们到目前为止的示例,我们将对我们的 config/session.php 文件进行以下更改:

'domain' => '.my-awesome-app.io',
Run Code Online (Sandbox Code Playgroud)

在本地,localhost单独是可以的:

'domain' => 'localhost',
Run Code Online (Sandbox Code Playgroud)

假设您已遵循 Sanctum 文档中的其余说明(设置axios.defaults.withCredentials = true;、添加中间件等),您的后端配置现已完成。

前端和身份验证。

我喜欢 Sanctum,我非常感谢创作者;所以我很尊重地这么说;文档在这一点上缺乏一点深度。获取 csrf 令牌非常简单,然后……

一旦 CSRF 保护被初始化,你应该向典型的 Laravel /login 路由发出 POST 请求。这个 /login 路由可能由 laravel/jetstream 认证脚手架包提供。如果登录请求成功,您将通过身份验证,随后对 API 路由的请求将通过 Laravel 后端发布给客户端的会话 cookie 自动进行身份验证。

看来他们已经更新了文档! 在我写这篇文章时,我已经检查了最新的文档,现在强调了一个事实,即您可以自由编写自己的登录端点。情况总是如此,但可能已经逃脱了一些人,也许是根据上面的说明(“您应该向典型的 Laravel /login 路由发出 POST 请求。”) 也可能不清楚您可以覆盖默认的 Laravel 方法来防止默认身份验证设置的不必要的副作用,例如重定向到 /home 页面等。

编写自己的登录控制器很简单,我更喜欢为 Sanctum 这样做。这是您可以使用的一个:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class LoginController extends Controller
{
  
  public function login(Request $request)
  {
    $request->validate([
      'email' => ['required', 'email'],
      'password' => 'required'
    ]);

    $credentials = $request->only('email', 'password');

    if (Auth::attempt($credentials)) {
      return response()->json(Auth::user(), 200);
    }

    throw ValidationException::withMessages([
      'email' => 'The provided credentails are incorect.'
    ]);

  }
}
Run Code Online (Sandbox Code Playgroud)

随意修改它以满足您的需求。

您如何在前端管理状态(例如,确保您的应用程序记住您已登录)也取决于您。有很多选择,但是如果我们使用 Sanctum,我认为我们应该专注于简单的基于 cookie 的方法。这是我使用的一种:

  1. 登录您的应用程序。身份验证会话已建立,您的浏览器会保存 Laravel API 提供的 cookie。
  2. 让您的登录脚本返回经过身份验证的用户(上面提供的用户就是这样做的)。将该用户的详细信息保存到您的应用程序状态(例如 Vuex)。
  3. 随时检查您的状态是否包含用户,以确保针对未授权用户的操作。重定向到登录页面是身份验证检查失败。

这是上面使用中间件的 Nuxt.js 形式。

/middleware/auth-check.js

export default async function ({ store, redirect }) {
  // Check if the user is not already in the store.
  if (store.state.user === null) {
    // Call your Laravel API to get the currently authenticated user.
    // It doesn't matter if the store has been wiped out due to a page
    // refresh- the browser still has the cookies, which will be sent
    // along with this request.
    try {
      let rsp = await user.getAuthenticatedUser()
      // If we get the user from the Laravel API, push it back in to
      // the store and carry on to the page.
      store.commit('SET_AUTH_USER', rsp.data)
    } catch (e) {
      // If our API doesn't return the user for any reason, redirect to
      // the login page.
      return redirect('/login')
    }
  }
  // If not, carry on to the page.
}


/pages/admin.vue

export default {
  middleware: auth-check
}
Run Code Online (Sandbox Code Playgroud)

上面的代码仅用于示例目的,但它通常是我用于 Vue/Nuxt 和 Sanctum 的代码。

希望这会有所帮助,如果有人可以受益,很乐意进一步详细说明。