Authentication
What you'll have afterwards:
- The sign-in methods you want, such as password, emailed link or passkeys.
- Server routes that only a signed-in user can call.
- Pages that a visitor is sent away from, to the sign-in page.
Auth lets people sign in and tells every server route who is calling. Users installs Auth for you and stores the users.
Prerequisites
A project created in Installation.
authentication, not auth. The auth key belongs to nuxt-auth-utils.1. Choose the sign-in methods
Turn methods on under authentication.settings in nuxt.config.ts:
| Method | Option | Default |
|---|---|---|
| Email and password | password | true (passwordReset follows it) |
| Magic link (emailed, single use) | magicLink | off |
| Passkeys | webAuthn | false (the starters set true) |
| OAuth providers (GitHub, Google, any OpenID Connect server, ...) | credentials in environment variables | off until configured |
export default defineNuxtConfig({
authentication: {
settings: {
afterLogin: '/account',
pages: { login: true, register: true },
magicLink: true,
},
},
})
pages.loginandpages.registeradd the/loginand/registerpages.afterLoginis where users land after signing in, unless a?redirect=was requested.- Passkeys need
@simplewebauthn/serverand@simplewebauthn/browserinstalled in the app. Users add a passkey on/account/security, and the sign-in page then shows a passkey button. - An OAuth provider turns on once its nuxt-auth-utils credentials exist, for example
NUXT_OAUTH_OIDC_CLIENT_ID. Without credentials its route answers404. - Register
<origin>/api/auth/<provider>as the callback URL at the OAuth provider.
log driver prints them in the terminal.The full list of options is on the Auth page.
2. Protect a server route
authorize checks that a request comes from a signed-in user who may do one thing, a
permission such as tickets:read, and returns who it is. It is auto-imported in server
code. Use it instead of nuxt-auth-utils' requireUserSession.
export default defineEventHandler(async (event) => {
const { user } = await authorize(event, 'tickets:read')
return getTickets(user.id)
})
| Call | Behaviour |
|---|---|
authorize(event, permission) | The principal, when it holds the permission. 401 without a valid credential, 403 insufficient_scope for a token without it, 403 forbidden for a user without it. |
authorize(event, permission, record) | Also runs the policies of the permission against the record. |
allows(event, permission, record?) | The same decision as a boolean. Never throws. |
requireAuth(event) | The principal from any credential, for routes every signed-in user may call. 401 without a valid one. |
requireAuth(event, { sessionOnly: true }) | Cookie sessions only. 403 session_required for tokens. |
getAuth(event) | The principal or null. Never throws. |
When you only need the user, requireUser(event, options) from Users returns just the user.
sessionOnly permission: authorize answers 403 session_required to a token. A leaked token must
not be able to do that. sessionOnly explains the rule.A permission has to be registered (modules do it for theirs with addPermission). An unregistered name is a 403 for
everyone.
3. Protect a page
Authorization decides who may open a page. The starter installs it. Add a rule to
routeRules in nuxt.config.ts:
export default defineNuxtConfig({
routeRules: {
'/members/**': { authenticated: true },
'/admin/**': { permission: 'admin:access', ssr: false },
},
})
A visitor is sent to authorization.redirectTo, which is /login in the starter. The requested page goes in
?redirect=, and the visitor comes back after signing in. A user without the permission gets 403.
For one page, use definePageMeta. Its rule replaces the matching route rules:
<script setup lang="ts">
definePageMeta({ authorize: 'events:manage' }) // a registered permission
</script>
Rules for /api/** are enforced too. So '/api/admin/**': { permission: 'admin:access' } guards the
staff API of every module.
/login?redirect=…. After signing in, you are
back on the page.4. Roles and the first admin
A user holds roles, and a role is a bundle of permissions.
Define them in authorization.roles; every signed-in user holds the defaultRoles:
export default defineNuxtConfig({
authorization: {
defaultRoles: ['user'],
roles: {
admin: { label: 'Admin', grant: ['*'] }, // '*' covers every permission except sensitive (:pii) ones
user: { label: 'User' }, // self-service permissions are implicit: every user holds them anyway
},
},
})
Admins assign roles on the user's page in the admin. The very first admin comes from the environment:
server/plugins/authorization-roles.ts adds admin to every user whose email is in NUXT_ACL_ADMINS, its access
control list (ACL), in the authorization:roles hook. Only once the address is verified (emailVerifiedAt): otherwise
anyone could sign up with an address on the list and become admin. The starter turns on verifyEmail, so the first
admin verifies through the emailed link.
// Loads the `authorization:roles` hook type (Nitro's server tsconfig does not include module types).
import type {} from '@frigear-nu/auth'
// Verified addresses in NUXT_ACL_ADMINS hold the `admin` role (`authorization.roles` in nuxt.config.ts). This bootstraps
// the first admin, who verifies through the emailed link and assigns every other role in the admin. Nothing is stored:
// removing an address removes the access. The user comes from `ctx.user`, never from the session on `ctx.event`.
export default defineNitroPlugin((nitro) => {
nitro.hooks.hook('authorization:roles', (ctx) => {
const admins = String(useRuntimeConfig().aclAdmins).split(',').map(email => email.trim().toLowerCase()).filter(Boolean)
// The stored user: an unverified address counts for nothing, since anyone can sign up with it.
const user = ctx.user as { email?: string | null, emailVerifiedAt?: unknown }
if (user.emailVerifiedAt && user.email && admins.includes(user.email.toLowerCase()) && !ctx.roles.includes('admin')) {
ctx.roles.push('admin')
}
})
})
The role is not stored: removing the address removes the access at once. Check what a signed-in user may do with
GET /api/auth/me/access.
Next steps
- Adding modules – bring in forms, payments, events and the rest.
- Protect a route – try route protection end to end.
- Auth and scopes – understand principals, scopes and staff in depth.
- Authorization – look up route rules, roles and policies.