如何在 Nuxt.js 应用中实现身份验证

介绍

在本教程中,您将使用模块Nuxt.js应用程序中实现身份验证Auth

出于本教程的目的,您将JWT用于身份验证。

下面是您将在本教程中构建的内容的快速演示:

显示用户登录的应用程序 gif 动画

您可以在 GitHub 上找到此应用程序源代码

警告:本教程中的几个包现在包含具有已知漏洞的依赖项。在生产环境中,您可以通过升级这些包、寻找替代方案或创建带有修补程序的分叉版本来解决这些问题。但是,在教程的有限上下文中,它按原样提供了教育价值。

先决条件

要完成本教程,您需要:

熟悉 Vue.js 和 Nuxt.js 可能会有所帮助。如果你开始使用 Nuxt.js,你可以参考这篇文章。

本教程已通过 Node v13.13.0、npm v6.14.4、vuev2.6.11 和nuxtv2.12.2 验证。

第 1 步 – 启动示例 API

您可以自由使用最适合您的任何框架。但是,为了快速开发,本教程将克隆一个使用AdonisJs构建的 API

API 使用:

API 具有三个端点:

  • /register: 用户注册端点
  • /login: 用于验证用户的端点
  • /me: 用于获取当前经过身份验证的用户的详细信息的端点,它受auth中间件保护,这意味着用户必须经过身份验证才能访问端点

首先,在终端窗口中运行以下命令:

  • git clone https://github.com/do-community/jwt-auth-api.git

然后,导航到项目目录:

  • cd jwt-auth-api

并安装 API 依赖项:

  • npm install

注意:运行安装时,您可能会遇到sqlite3版本问题,4.0.1具体取决于您运行的 Node 版本。请参阅变更日志以确定与您的环境的兼容性。

在最初发布时,Node 的最新版本是 10。一种选择是将您的 Node 版本降级到10.20.1(了解它即将结束生命支持)。然后,运行npm install

第二个选项是删除package-lock.json文件,这将导致系统查找4.2.0最高支持 Node 13 的文件。您可能还需要将 Node 版本降级到13.13.0. 然后,运行npm install

第三种选择是修改package.jsonsqlite3您当前版本的 Node 支持的版本,删除package-lock.json并运行npm install但是,在测试时,5.0.0尚未发布以处理 Node 14+ 支持。

不兼容的其他症状包括以下错误:TypeError: Cannot read property 'data' of undefinedError: Cannot find module '[...]/node_modules/sqlite3/lib/binding/[...]/node_sqlite3.node'.

接下来,重命名.env.example.env

  • mv .env.example .env

并生成一个APP_KEY

  • npx @adonisjs/cli@4.0.12 key:generate

你应该看到:

Output
  • generated: unique APP_KEY

一旦完成,让我们运行迁移:

  • npx @adonisjs/cli@4.0.12 migration:run

现在,您可以启动 API:

  • # ensure that you are in the `jwt-auth-api` project directory
  • npm start

您可以在 上访问 API http://127.0.0.1:3333/api在本教程的剩余时间里,让它在终端窗口中运行。

第 2 步 – 创建 Nuxt.js 应用程序

现在,您可以创建一个 Nuxt.js 应用程序。打开一个新的终端窗口并使用vue-cliNuxt starter 模板来初始化一个新的 Vue 项目:

注意:在测试时,vue-cli已弃用。@vue/cli是当前 Vue 项目的命令行工具。并且@vue/cli-init是遗留vue-cli项目的推荐方法但是,这create-nuxt-app是现代 Nuxt 项目的推荐方法。

接下来,您需要导航到项目目录:

  • cd nuxt-auth

并安装依赖项:

npm install

然后,您可以启动该应用程序:

  • npm run dev

该应用程序应该在http://localhost:3000. 您可以在 Web 浏览器中查看该应用程序,以查看由vue-cli.

第 3 步 – 安装必要的 Nuxt.js 模块

现在,让我们安装您的应用程序所需的 Nuxt.js 模块。您将使用Nuxt Auth 模块Nuxt Axios 模块,因为该auth模块在内部使用 Axios:

  • # ensure that you are in the `nuxt-auth` project directory
  • npm install @nuxtjs/auth@4.5.1 @nuxtjs/axios@5.3.1 --save

完成后,打开nuxt.config.js

  • nano nuxt.config.js

将下面的代码添加到nuxt.config.js

nuxt.config.js
module.exports = {
  // ...

  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],
}

注意:此时,较新版本的 Nuxt 可能会遇到错误:Enable vuex store by creating 'store/index.js'. 可以通过目录中添加一个空index.js文件来store解决此错误

接下来,您需要设置模块。将下面的代码粘贴到nuxt.config.js

nuxt.config.js
module.exports = {
  // ...

  axios: {
    baseURL: 'http://127.0.0.1:3333/api'
  },

  auth: {
    strategies: {
      local: {
        endpoints: {
          login: { url: 'login', method: 'post', propertyName: 'data.token' },
          user: { url: 'me', method: 'get', propertyName: 'data' },
          logout: false
        }
      }
    }
  }
}

在这里,您设置 Axios 在发出请求时将使用的基本 URL。在我们的例子中,我们引用了我们之前设置的示例 API。

然后,为与localAPI 上策略相对应策略定义身份验证端点

  • 成功验证后,令牌将作为token对象内的data对象出现在响应中
  • 同样,来自/me端点的响应将在data对象内部
  • 最后,您设置logout为 ,false因为您的 API 没有用于注销的端点。当用户注销时,您只需从 localStorage 中删除令牌。

第 4 步 – 创建导航栏组件

要设计您的应用程序,您可以使用Bulma

nuxt.config.jslink对象内部的head对象中打开并粘贴以下代码

nuxt.config.js
module.exports = {
  // ...
  head: {
    // ...
    link [
      // ...
      {
        rel: 'stylesheet',
        href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css'
      }
    ]
  },
  // ...
}

现在,让我们创建 Navbar 组件:

  • nano components/Navbar.vue

并添加以下代码:

组件/Navbar.vue
<template>
  <nav class="navbar is-light">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
        <button class="button navbar-burger">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item has-dropdown is-hoverable">
            <a class="navbar-link">
              My Account
            </a>
            <div class="navbar-dropdown">
              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
              <hr class="navbar-divider"/>
              <a class="navbar-item">Logout</a>
            </div>
          </div>
          <template>
            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
          </template>
        </div>
      </div>
    </div>
  </nav>
</template>

导航栏组件包含链接loginregisterprofile,和logout

接下来,让我们更新默认布局以使用该Navbar组件。

打开default.vue:

  • nano layouts/default.vue

并将内容替换为以下内容:

布局/default.vue
<template>
  <div>
    <Navbar/>
    <nuxt/>
  </div>
</template>

<script>
import Navbar from '~/components/Navbar'

export default {
  components: {
    Navbar
  }
}
</script>

另外,让我们更新主页。

打开index.vue:

  • nano pages/index.vue

并将内容替换为以下内容:

页面/index.vue
<template>
  <section class="section">
    <div class="container">
      <h1 class="title">Nuxt Auth</h1>
    </div>
  </section>
</template>

此时,您应该有一个显示标题的应用程序,"Nuxt Auth"带有带有导航链接的标题栏:

带有标题和标题栏的应用页面

步骤 5 — 处理用户注册

pages目录中,创建一个新register.vue文件:

  • nano pages/register.vue

并添加以下代码:

页面/注册.vue
<template>
  <section class="section">
    <div class="container">
      <div class="columns">
        <div class="column is-4 is-offset-4">
          <h2 class="title has-text-centered">Register!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="register">
            <div class="field">
              <label class="label">Username</label>
              <div class="control">
                <input
                  type="text"
                  class="input"
                  name="username"
                  v-model="username"
                  required 
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input
                  type="email"
                  class="input"
                  name="email"
                  v-model="email"
                  required
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  name="password"
                  v-model="password"
                  required
                />
              </div>
            </div>
            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">Register</button>
            </div>
          </form>

          <div class="has-text-centered" style="margin-top: 20px">
            Already got an account? <nuxt-link to="/login">Login</nuxt-link>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import Notification from '~/components/Notification'

export default {
  components: {
    Notification,
  },

  data() {
    return {
      username: '',
      email: '',
      password: '',
      error: null
    }
  },

  methods: {
    async register() {
      try {
        await this.$axios.post('register', {
          username: this.username,
          email: this.email,
          password: this.password
        })

        await this.$auth.loginWith('local', {
          data: {
          email: this.email,
          password: this.password
          },
        })

        this.$router.push('/')
      } catch (e) {
        this.error = e.response.data.message
      }
    }
  }
}
</script>

这包含三个域的形式:usernameemail,和password每个字段都绑定到组件上的相应数据。提交表单时,register将调用一个方法。使用 Axios 模块,您可以向/register端点发出 post 请求,并传递用户数据。如果注册成功,您使用 Auth 模块的loginWith(),使用该local策略并传递用户数据来登录用户。然后,您将用户重定向到主页。如果注册过程中出现错误,则将error数据设置为从 API 响应中获取的错误消息。

如果出现错误,则错误消息由 Notification 组件显示。

Notification.vue在里面创建一个新文件components

  • nano components/Notifaction.vue

并将下面的代码粘贴到其中:

组件/Notification.vue
<template>
  <div class="notification is-danger">
    {{ message }}
  </div>
</template>

<script>
export default {
  name: 'Notification',
  props: ['message']
}
</script>

Notification 组件接受一个messageprops,它是错误信息。

现在,您可以测试用户注册:

带有用户名、电子邮件和密码字段的注册页面

注册页面,但会向用户显示出现错误的通知消息

第 6 步 – 处理登录和注销的 LoggedUsers

注册成功后,用户应该已登录,但目前应用程序无法知道用户是否已登录。所以让我们通过更新 Navbar 组件并添加一些计算属性来解决这个问题。

在你这样做之前,让我们首先通过index.jsstore目录中创建一个文件来激活 Vuex 商店Auth 模块将用户身份验证状态以及 Vuex 状态内的用户详细信息存储在一个auth对象中。因此,您可以使用 来检查用户是否已登录this.$store.state.auth.loggedIn,这将返回truefalse同样,您可以使用 获取用户的详细信息this.$store.state.auth.usernull如果没有用户登录,则为。

注意:您还可以分别使用this.$auth.loggedIn直接通过 Auth 模块访问用户身份验证状态以及用户详细信息this.$auth.user

由于您可能希望在应用程序的多个位置使用计算属性,让我们创建商店 getter。

打开index.js:

  • nano store/index.js

并将下面的代码粘贴到其中:

商店/index.js
export const getters = {
  isAuthenticated(state) {
    return state.auth.loggedIn
  },

  loggedInUser(state) {
    return state.auth.user
  }
}

在这里,您创建了两个 getter。第一个 ( isAuthenticated) 将返回用户的身份验证状态,第二个 ( loggedInUser) 将返回详细信息或登录用户。

接下来,让我们更新 Navbar 组件以使用 getter。将 的内容替换components/Navbar.vue为以下内容:

组件/Navbar.vue
<template>
  <nav class="navbar is-light">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
        <button class="button navbar-burger">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">
            <a class="navbar-link">
              {{ loggedInUser.username }}
            </a>
            <div class="navbar-dropdown">
              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
              <hr class="navbar-divider"/>
              <a class="navbar-item">Logout</a>
            </div>
          </div>
          <template v-else>
            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
          </template>
        </div>
      </div>
    </div>
  </nav>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['isAuthenticated', 'loggedInUser'])
  }
}
</script>

您可以通过使用扩展运算符 ( ...) 从 中提取 getter 来创建计算属性mapGetters然后使用isAuthenticated中,显示用户菜单或链接loginregister根据用户是否正在或没有登录。此外,您还loggedInUser用于显示经过身份验证的用户用户名。

现在,如果您刷新应用程序,您应该会看到类似于以下内容的内容:

标题中包含用户用户名的应用页面

第 7 步 – 处理用户登录

现在,让我们允许返回用户登录。

login.vuepages目录中创建一个新文件

nano pages/login.vue

并将下面的代码粘贴到其中:

页面/登录.vue
<template>
  <section class="section">
    <div class="container">
      <div class="columns">
        <div class="column is-4 is-offset-4">
          <h2 class="title has-text-centered">Welcome back!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="login">
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input
                  type="email"
                  class="input"
                  name="email"
                  v-model="email"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  name="password"
                  v-model="password"
                />
              </div>
            </div>
            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">Log In</button>
            </div>
          </form>
          <div class="has-text-centered" style="margin-top: 20px">
            <p>
              Don't have an account? <nuxt-link to="/register">Register</nuxt-link>
            </p>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import Notification from '~/components/Notification'

export default {
  components: {
    Notification,
  },

  data() {
    return {
      email: '',
      password: '',
      error: null
    }
  },

  methods: {
    async login() {
      try {
        await this.$auth.loginWith('local', {
          data: {
          email: this.email,
          password: this.password
          }
        })

        this.$router.push('/')
      } catch (e) {
        this.error = e.response.data.message
      }
    }
  }
}
</script>

这与register页面非常相似该表单包含两个字段:emailpassword提交表单时,login将调用一个方法。使用 Auth 模块loginWith()并传递用户数据,您登录用户。如果身份验证成功,您将用户重定向到主页。否则,设置error为从 API 响应中获取的错误消息。同样,您使用之前的 Notification 组件来显示错误消息。

应用欢迎返回页面包含两个字段:电子邮件和密码

步骤 8 — 显示用户配置文件

让我们允许登录用户查看他们的个人资料。

profile.vuepages目录中创建一个新文件

  • nano pages/profile.vue

并将下面的代码粘贴到其中:

页面/profile.vue
<template>
  <section class="section">
    <div class="container">
      <h2 class="title">My Profile</h2>
      <div class="content">
        <p>
          <strong>Username:</strong>
          {{ loggedInUser.username }}
        </p>
        <p>
          <strong>Email:</strong>
          {{ loggedInUser.email }}
        </p>
      </div>
    </div>
  </section>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['loggedInUser'])
  }
}
</script>

请注意您之前是如何使用loggedInUsergetter 来显示用户详细信息的。

单击“我的个人资料”链接应该会显示“我的个人资料”页面。

我的个人资料页面显示用户名和电子邮件

步骤 9 — 注销用户

更新导航栏组件内的注销链接。

打开Navbar.vue:

  • nano components/Navbar.vue

修改注销链接以使用@click="logout"

组件/Navbar.vue
// ...
<div class="navbar-dropdown">
  <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
  <hr class="navbar-divider"/>
  <a class="navbar-item"  @click="logout">Logout</a>
</div>
// ...

当注销链接被点击时,它会触发一个logout方法。

接下来,让我们logout在 Navbar 组件的脚本部分添加方法:

组件/Navbar.vue
// ...

export default {
  // ...
  methods: {
    async logout() {
      await this.$auth.logout();
    },
  },
}

您调用logout()Auth 模块的 。这将从本地存储中删除用户的令牌并将用户重定向到主页。

第 10 步 – 限制个人资料页面

就目前而言,任何人都可以访问该profile页面。如果用户未登录,则会导致错误。

应用程序页面上的类型错误

要解决此问题,您需要将个人资料页面限制为仅登录用户。幸运的是,您可以使用 Auth 模块实现这一点。Auth 模块带有一个auth中间件,您可以在这种情况下使用它。

所以让我们将auth中间件添加profile页面中。更新script部分如下:

页面/profile.vue
// ...

export default {
  middleware: 'auth',
  // ...
}

现在,当未登录的用户尝试访问该profile页面时,该用户将被重定向到该login页面。

第 11 步 – 创建访客中间件

同样,即使作为登录用户,您仍然可以访问登录和注册页面。解决此问题的一种方法是将登录和注册页面仅限于未登录的用户。您可以通过创建访客中间件来做到这一点。

middleware目录中,创建一个新guest.js文件:

  • nano middleware/guest.js

并将下面的代码粘贴到其中:

中间件/guest.js
export default function ({ store, redirect }) {
  if (store.state.auth.loggedIn) {
    return redirect('/')
  }
}

中间件接受上下文作为它的第一个参数。所以你从上下文中提取storeredirect然后,您检查用户是否已登录,然后将用户重定向到主页。否则,您允许正常执行请求。

接下来,让我们利用这个中间件。更新script两部分login,并register如下:

pages/login.vue 和 pages/register.vue
// ...

export default {
  middleware: 'guest',
  // ...
}

现在,一切都会按预期进行。

结论

在本教程中,您了解了如何使用 Auth 模块在 Nuxt.js 应用程序中实现身份验证。您还看到了如何通过使用中间件来保持身份验证流程顺畅。

要了解有关 Auth 模块的更多信息,请查看文档

如果您想了解有关 Vue.js 的更多信息,请查看我们的 Vue.js 主题页面以获取练习和编程项目。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁