Nuxt 3 中的身份验证
如何在 Nuxt 3 中添加身份验证
我看过一些关于这个主题的教程,但大多数教程都涵盖了使用 Supabase、Amplify 或 Firebase 进行身份验证,这些服务大多都有一个 Nuxt 组件,可以更轻松地为您的网站添加身份验证。
如果你和我一样,使用中间件通过调用提供令牌的端点来处理应用程序的身份验证状态,那么我将向你展示如何在 Nuxt 3 中实现这一点。
我将使用DummyJSON 的模拟 API 来帮助我完成这项工作。
什么是 DummyJSON?
使用 DummyJSON,您可以获得不同类型的 REST 端点,其中填充了 JSON 数据,您可以在使用自己喜欢的框架和库开发前端时使用这些端点,而无需担心编写后端。
本质上,它是一个模拟 API,只有几个端点,最重要的是它提供了一个 REST 登录端点,该端点会返回一个伪造的令牌。
创建项目
首先,让我们从创建一个项目开始。
npx nuxi init nuxt3-auth
在项目根目录下创建以下文件夹/文件
-
页面
- index.vue
- 登录.vue
- 关于.vue
-
布局
- default.vue
删除app.vue
创建所需页面
pages/index.vue
<template>
<div>Hello Home Page</div>
</template>
<script lang="ts" setup></script>
pages/about.vue
<template>
<div>About Page</div>
</template>
<script lang="ts" setup></script>
登录页面会非常简洁,只有用户名和密码输入框以及一个登录按钮。
pages/login.vue
<template>
<div>
<div class="title">
<h2>Login</h2>
</div>
<div class="container form">
<label for="uname"><b>Username</b></label>
<input
v-model="user.username"
type="text"
class="input"
placeholder="Enter Username"
name="uname"
required
/>
<label for="psw"><b>Password</b></label>
<input
v-model="user.password"
type="password"
class="input"
placeholder="Enter Password"
name="psw"
required
/>
<button @click.prevent="login" class="button">Login</button>
</div>
</div>
</template>
<script lang="ts" setup>
const user = ref({
username: '',
password: '',
});
const login = async () => {
// TODO send user Data to the login endpoint and redirect if successful
};
</script>
创建我们的默认布局
Nuxt 提供了一个可自定义的布局框架,您可以在整个应用程序中使用它,非常适合将常见的 UI 或代码模式提取为可重用的布局组件。
布局文件放置在layouts/指定目录中,并在使用时通过异步导入自动加载。
我们的布局将包含一个导航栏,其中包含“首页”、“关于我们”和“登录”链接,底部是一个页脚,中间是我们的内容,这些内容<slot/>将自动替换为页面中的代码。
layouts/default.vue
<template>
<div>
<header>
<ul>
<li><nuxt-link to="/">Home</nuxt-link></li>
<li><nuxt-link to="/about">About</nuxt-link></li>
<li v-if="!authenticated" class="loginBtn" style="float: right">
<nuxt-link to="/login">Login</nuxt-link>
</li>
</ul>
</header>
<div class="mainContent">
<slot />
</div>
<footer>
<h1>Footer</h1>
</footer>
</div>
</template>
中间件
Nuxt 提供了一个可定制的路由中间件框架,您可以在整个应用程序中使用它,非常适合提取在导航到特定路由之前需要运行的代码。
路由中间件是导航守卫,它接收当前路由和下一个路由作为参数。
我们将创建一个命名路由中间件,它位于指定middleware/目录中,当页面使用该中间件时,它将通过异步导入自动加载。
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
console.log('From auth middleware')
})
添加控制台日志以验证其在我们的主页上是否正常工作,现在我们可以添加此中间件了。
pages/index.vue
<template>
<div>Hello Home Page</div>
</template>
<script lang="ts" setup>
definePageMeta({
middleware: 'auth' // this should match the name of the file inside the middleware directory
})
</script>
运行代码并检查验证是否可以看到console.log
在这种情况下,我想保护所有路由,那么如何使这个中间件全局生效呢?我们只需要.global像这样在文件中添加后缀auth.global.ts,它就会在每次路由更改时自动运行。
现在我们可以从首页移除这段代码了。
definePageMeta({
middleware: 'auth' // this should match the name of the file inside the middleware directory
})
与皮尼亚一起购物
我打算创建一个身份验证存储来处理登录和已验证状态。我已经写过一篇文章介绍如何在 Nuxt 3 中设置 Pinia。Pinia和 Nuxt 3
// store/auth.ts
import { defineStore } from 'pinia';
interface UserPayloadInterface {
username: string;
password: string;
}
export const useAuthStore = defineStore('auth', {
state: () => ({
authenticated: false,
loading: false,
}),
actions: {
async authenticateUser({ username, password }: UserPayloadInterface) {
// useFetch from nuxt 3
const { data, pending }: any = await useFetch('https://dummyjson.com/auth/login', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: {
username,
password,
},
});
this.loading = pending;
if (data.value) {
const token = useCookie('token'); // useCookie new hook in nuxt 3
token.value = data?.value?.token; // set token to cookie
this.authenticated = true; // set authenticated state value to true
}
},
logUserOut() {
const token = useCookie('token'); // useCookie new hook in nuxt 3
this.authenticated = false; // set authenticated state value to false
token.value = null; // clear the token cookie
},
},
});
我们有两个行动authenticateUser方案,logUserOut
authenticateUser函数接收用户名和密码作为有效负载,然后我们使用useFetch钩子函数向dummyjson/auth/login中的端点发送 POST 请求,并在请求体中传递用户名和密码。 我们应该收到类似这样的响应。
{
"id": 15,
"username": "kminchelle",
"email": "kminchelle@qq.com",
"firstName": "Jeanne",
"lastName": "Halvorson",
"gender": "female",
"image": "https://robohash.org/autquiaut.png?size=50x50&set=set1",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsInVzZXJuYW1lIjoia21pbmNoZWxsZSIsImVtYWlsIjoia21pbmNoZWxsZUBxcS5jb20iLCJmaXJzdE5hbWUiOiJKZWFubmUiLCJsYXN0TmFtZSI6IkhhbHZvcnNvbiIsImdlbmRlciI6ImZlbWFsZSIsImltYWdlIjoiaHR0cHM6Ly9yb2JvaGFzaC5vcmcvYXV0cXVpYXV0LnBuZz9zaXplPTUweDUwJnNldD1zZXQxIiwiaWF0IjoxNjM1NzczOTYyLCJleHAiOjE2MzU3Nzc1NjJ9.n9PQX8w8ocKo0dMCw3g8bKhjB8Wo7f7IONFBDqfxKhs"
}
如果有数据,我们会将令牌保存到 cookie 中。
logUserOut此函数只是简单地从 cookie 中删除令牌。
最终确定
现在我们需要修改中间件、登录和布局。
登录页面
现在我们可以导入身份验证存储并完成登录功能。
pages/login.vue
<script lang="ts" setup>
import { storeToRefs } from 'pinia'; // import storeToRefs helper hook from pinia
import { useAuthStore } from '~/store/auth'; // import the auth store we just created
const { authenticateUser } = useAuthStore(); // use authenticateUser action from auth store
const { authenticated } = storeToRefs(useAuthStore()); // make authenticated state reactive with storeToRefs
const user = ref({
username: 'kminchelle',
password: '0lelplR',
});
const router = useRouter();
const login = async () => {
await authenticateUser(user.value); // call authenticateUser and pass the user object
// redirect to homepage if user is authenticated
if (authenticated) {
router.push('/');
}
};
</script>
布局
在默认布局中,我们会根据应用程序的状态显示登录/注销按钮,并处理注销事件。
authenticated根据状态调整导航栏以显示或隐藏按钮
<li v-if="!authenticated" class="loginBtn" style="float: right">
<nuxt-link to="/login">Login</nuxt-link>
</li>
<li v-if="authenticated" class="loginBtn" style="float: right">
<nuxt-link @click="logout">Logout</nuxt-link>
</li>
将以下内容添加到脚本中layouts/default.vue
<script lang="ts" setup>
import { storeToRefs } from 'pinia'; // import storeToRefs helper hook from pinia
import { useAuthStore } from '~/store/auth'; // import the auth store we just created
const router = useRouter();
const { logUserOut } = useAuthStore(); // use authenticateUser action from auth store
const { authenticated } = storeToRefs(useAuthStore()); // make authenticated state reactive with storeToRefs
const logout = () => {
logUserOut();
router.push('/login');
};
</script>
中间件
现在,在中间件中,我们可以根据 cookie 中令牌的值来处理身份验证。
export default defineNuxtRouteMiddleware((to) => {
const { authenticated } = storeToRefs(useAuthStore()); // make authenticated state reactive
const token = useCookie('token'); // get token from cookies
if (token.value) {
// check if value exists
authenticated.value = true; // update the state to authenticated
}
// if token exists and url is /login redirect to homepage
if (token.value && to?.name === 'login') {
return navigateTo('/');
}
// if token doesn't exist redirect to log in
if (!token.value && to?.name !== 'login') {
abortNavigation();
return navigateTo('/login');
}
});
预览
仓库:GitHub
文章来源:https://dev.to/rafaelmagalhaes/authentication-in-nuxt-3-375o