Authentication overview
Authentication means checking who the user is: they enter an email and a password, the server verifies them and remembers the login. Remembering is called a session: the server puts user data into an encrypted cookie and knows who is calling on later requests. In Nuxt a ready-made session comes from the nuxt-auth-utils module. This lesson is an overview: a full system with sign-up and a user database is built on the same functions.
Installing
npx nuxi module add auth-utilsThe module needs a cookie encryption key at least 32 characters long in the NUXT_SESSION_PASSWORD variable. During development the module writes one into .env itself; on the production server set it by hand.
Signing in on the server
For the example the blog has one author, and the password is kept in the adminPassword setting from the environment variables lesson. Create server/api/login.post.ts:
// server/api/login.post.ts
export default defineEventHandler(async (event) => {
const { email, password } = await readBody<{ email?: string; password?: string }>(event)
const config = useRuntimeConfig(event)
if (email !== 'anna@example.com' || password !== config.adminPassword) {
throw createError({ statusCode: 401, message: 'Wrong email or password' })
}
await setUserSession(event, { user: { name: 'Anna', email } })
return { ok: true }
})setUserSession writes the user into the session. In a real project users live in a database and passwords are stored as hashes; the module provides the server functions hashPassword and verifyPassword for that.
The login page
<!-- app/pages/login.vue -->
<script setup lang="ts">
const { fetch: refreshSession } = useUserSession()
const form = reactive({ email: '', password: '' })
async function login() {
await $fetch('/api/login', { method: 'POST', body: form })
await refreshSession()
await navigateTo('/admin')
}
</script>
<template>
<form @submit.prevent="login">
<input v-model="form.email" type="email" placeholder="Email">
<input v-model="form.password" type="password" placeholder="Password">
<button>Sign in</button>
</form>
</template>useUserSession() holds the session on the page side. After a successful login refreshSession reloads it, and the user lands on /admin.
Who is signed in
<!-- app/layouts/default.vue -->
<script setup lang="ts">
const { loggedIn, user, clear } = useUserSession()
</script>
<template>
<header>
<template v-if="loggedIn">
<span>{{ user?.name }}</span>
<button @click="clear">Sign out</button>
</template>
<NuxtLink v-else to="/login">Sign in</NuxtLink>
</header>
</template>After signing in, the header shows the name "Anna" and a "Sign out" button; clear removes the session.
Protecting pages and the API
The middleware app/middleware/auth.ts now checks the session instead of the token cookie:
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware(() => {
const { loggedIn } = useUserSession()
if (!loggedIn.value) {
return navigateTo('/login')
}
})Attach it not only to app/pages/admin.vue but also to the form app/pages/posts/new.vue: definePageMeta({ middleware: 'auth' }). And the handler that creates posts checks the session itself:
// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
await requireUserSession(event)
// then the previous code that creates a post
})requireUserSession answers with a 401 error if the request has no session.
Common mistake
Protecting only the /posts/new page and forgetting the API. The middleware keeps guests off the page, but a POST /api/posts request can be sent directly, for example with curl. Check the login in every server handler that changes data: protection on pages is for convenience, the real one is on the server.