[![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![License][license-src]][license-href] [![Nuxt][nuxt-src]][nuxt-href]
Add Authentication to Nuxt applications with secured & sealed cookies sessions.
useUserSession() Vue composable<AuthState> componentIt has few dependencies (only from UnJS), run on multiple JS environments (Node, Deno, Workers) and is fully typed with TypeScript.
This module only works with a Nuxt server running as it uses server API routes (nuxt build).
This means that you cannot use this module with nuxt generate.
You can anyway use Hybrid Rendering to pre-render pages of your application or disable server-side rendering completely.
nuxt-auth-utils in your Nuxt projectnpx nuxi@latest module add auth-utils
NUXT_SESSION_PASSWORD env variable with at least 32 characters in the .env.# .env
NUXT_SESSION_PASSWORD=password-with-at-least-32-characters
Nuxt Auth Utils generates one for you when running Nuxt in development the first time if no NUXT_SESSION_PASSWORD is set.
Nuxt Auth Utils automatically adds some plugins to fetch the current user session to let you access it from your Vue components.
<script setup>
const { loggedIn, user, session, fetch, clear, openInPopup } = useUserSession()
</script>
<template>
<h1>Welcome {{ user.login }}!</h1>
Logged in since {{ session.loggedInAt }}
<button @click="clear">Logout</button>
<h1>Not logged in</h1>
<a href="https://github.com/atinux/nuxt-auth-utils/raw/v0.5.29/auth/github">Login with GitHub</a>
<button @click="openInPopup('/auth/github')">Login with GitHub</button>
</template>
TypeScript Signature:
interface UserSessionComposable {
/**
* Computed indicating if the auth session is ready
*/
ready: ComputedRef<boolean>
/**
* Computed indicating if the user is logged in.
*/
loggedIn: ComputedRef<boolean>
/**
* The user object if logged in, null otherwise.
*/
user: ComputedRef<User | null>
/**
* The session object.
*/
session: Ref<UserSession>
/**
* Fetch the user session from the server.
*/
fetch: () => Promise<void>
/**
* Clear the user session and remove the session cookie.
*/
clear: () => Promise<void>
/**
* Open the OAuth route in a popup that auto-closes when successful.
*/
openInPopup: (route: string, size?: { width?: number, height?: number }) => void
}
[!IMPORTANT] Nuxt Auth Utils uses the
/api/_auth/sessionroute for session management. Ensure your API route middleware doesn't interfere with this path.
The following helpers are auto-imported in your server/ directory.
// Set a user session, note that this data is encrypted in the cookie but can be decrypted with an API call
// Only store the data that allow you to recognize a user, but do not store sensitive data
// Merges new data with existing data using unjs/defu library
await setUserSession(event, {
// User data
user: {
login: 'atinux'
},
// Private data accessible only on server/ routes
secure: {
apiToken: '1234567890'
},
// Any extra fields for the session data
loggedInAt: new Date()
})
// Replace a user session. Same behaviour as setUserSession, except it does not merge data with existing data
await replaceUserSession(event, data)
// Get the current user session
const session = await getUserSession(event)
// Clear the current user session
await clearUserSession(event)
// Require a user session (send back 401 if no `user` key in session)
const session = await requireUserSession(event)
You can define the type for your user session by creating a type declaration file (for example, auth.d.ts) in your project to augment the UserSession type:
// shared/types/auth.d.ts
declare module '#auth-utils' {
interface User {
// Add your own fields
}
interface UserSession {
// Add your own fields
}
interface SecureSessionData {
// Add your own fields
}
}
export {}
[!IMPORTANT] Since we encrypt and store session data in cookies, we're constrained by the 4096-byte cookie size limit. Store only essential information.
All handlers can be auto-imported and used in your server routes or API routes.
The pattern is defineOAuth<Provider>EventHandler({ onSuccess, config?, onError? }), example: defineOAuthGitHubEventHandler.
The helper returns an event handler that automatically redirects to the provider authorization page and then calls onSuccess or onError depending on the result.
The config can be defined directly from the runtimeConfig in your nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
oauth: {
// provider in lowercase (github, google, etc.)
<provider>: {
clientId: '...',
clientSecret: '...'
}
}
}
})
It can also be set using environment variables:
NUXT_OAUTH_<PROVIDER>_CLIENT_IDNUXT_OAUTH_<PROVIDER>_CLIENT_SECRETProvider is in uppercase (GITHUB, GOOGLE, etc.)
You can add your favorite provider by creating a new file in src/runtime/server/lib/oauth/.
Example: ~/server/routes/auth/github.get.ts
export default defineOAuthGitHubEventHandler({
config: {
emailRequired: true
},
async onSuccess(event, { user, tokens }) {
await setUserSession(event, {
user: {
githubId: user.id
}
})
return sendRedirect(event, '/')
},
// Optional, will return a json error and 401 status code by default
onError(event, error) {
console.error('GitHub OAuth error:', error)
return sendRedirect(event, '/')
},
})
Make sure to set the callback URL in your OAuth app settings as <your-domain>/auth/github.
If the redirect URL mismatch in production, this means that the module cannot guess the right redirect URL. You can set the NUXT_OAUTH_<PROVIDER>_REDIRECT_URL env variable to overwrite the default one.
Nuxt Auth Utils provides password hashing utilities like hashPassword and verifyPassword to hash and verify passwords by using scrypt as it is supported in many JS runtime.
const hashedPassword = await hashPassword('user_password')
if (await verifyPassword(hashedPassword, 'user_password')) {
// Password is valid
}
It also provides a passwordNeedsRehash function to check if a password needs to be rehashed. This is useful when the hash settings are changed, such as as increasing the scrypt cost parameters.
const needsRehash = passwordNeedsRehash(hashedPassword)
if (needsRehash) {
// Password needs to be rehashed
hashedPassword = await hashPassword('user_password')
}
You can configure the scrypt options in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-auth-utils'],
auth: {
hash: {
scrypt: {
// See https://github.com/adonisjs/hash/blob/94637029cd526783ac0a763ec581306d98db2036/src/types.ts#L144
}
}
}
})
Social networks that rely on AT Protocol (e.g., Bluesky) slightly differ from a regular OAuth flow.
To enable OAuth with AT Protocol, you need to:
npx nypm i @atproto/oauth-client-node @atproto/api
nuxt.config.tsexport default defineNuxtConfig({
auth: {
atproto: true
}
})
WebAuthn (Web Authentication) is a web standard that enhances security by replacing passwords with passkeys using public key cryptography. Users can authenticate with biometric data (like fingerprints or facial recognition) or physical devices (like USB keys), reducing the risk of phishing and password breaches. This approach offers a more secure and user-friendly authentication method, supported by major browsers and platforms.
To enable WebAuthn you need to:
npx nypm i @simplewebauthn/server@11 @simplewebauthn/browser@11
nuxt.config.tsexport default defineNuxtConfig({
auth: {
webAuthn: true
}
})
In this example we will implement the very basic steps to register and authenticate a credential.
The full code can be found in the playground. The example uses a SQLite database with the following minimal tables:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS credentials (
userId INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
id TEXT UNIQUE NOT NULL,
publicKey TEXT NOT NULL,
counter INTEGER NOT NULL,
backedUp INTEGER NOT NULL,
transports TEXT NOT NULL,
PRIMARY KEY ("userId", "id")
);
users table it is important to have a unique identifier such as a username or email (here we use email). When creating a new credential, this identifier is required and stored with the passkey on the user's device, password manager, or authenticator.credentials table stores:userId from the users table.id (as unique index)publicKeycounter. Each time a credential is used, the counter is incremented. We can use this value to perform extra security checks. More about counter can be read here. For this example, we won't be using the counter. But you should update the counter in your database with the new value.backedUp flag. Normally, credentials are stored on the generating device. When you use a password manager or authenticator, the credential is "backed up" because it can be used on multiple devices. See this section for more details.transports. It is an array of strings that indicate how the credential communicates with the client. It is used to show the correct UI for the user to utilize the credential. Again, see this section for more details.The following code does not include the actual database queries, but shows the general steps to follow. The full example can be found in the playground: registration, authentication and the database setup.
// server/api/webauthn/register.post.ts
import { z } from 'zod'
export default defineWebAuthnRegisterEventHandler({
// optional
async validateUser(userBody, event) {
// bonus: check if the user is already authenticated to link a credential to his account
// We first check if the user is already authenticated by getting the session
// And verify that the email is the same as the one in session
const session = await getUserSession(event)
if (session.user?.email && session.user.email !== userBody.userName) {
throw createError({ statusCode: 400, message: 'Email not matching curent session' })
}
// If he registers a new account with credentials
return z.object({
// we want the userName to be a valid email
userName: z.string().email()
}).parse(userBody)
},
async onSuccess(event, { credential, user }) {
// The credential creation has been successful
// We need to create a user if it does not exist
const db = useDatabase()
// Get the user from the database
let dbUser = await db.sql`...`
if (!dbUser) {
// Store new user in database & its credentials
dbUser = await db.sql`...`
}
// we now need to store the credential in our database and link it to the user
await db.sql`...`
// Set the user session
await setUserSession(event, {
user: {
id: dbUser.id
},
loggedInAt: Date.now(),
})
},
})
[!TIP] If you want to plug in and use the [Community sourced list
$ claude mcp add nuxt-auth-utils \
-- python -m otcore.mcp_server <graph>