Client
The browser needs to know what to show: which buttons, menu items and pages. This page covers the composable and components that answer that, the route rules that guard pages, and how the same code works in client mode and mobile apps. Set up the module first: Overview.
authorize(event, ...) or route rules.useAuthorization()
const { access, can, cannot, authorize, refresh } = useAuthorization()
| Name | Behaviour |
|---|---|
access | { kind, scopes, permissions, roles? } as GET /api/auth/me/access answers it, or null for visitors. |
can(permission, record?) | Synchronous. Whether the exact access.permissions list holds the permission, then the shared policies for it, which can only deny. |
cannot(permission, record?) | The opposite of can. |
authorize(permission, record?) | Throws a Nuxt error when can is false: 401 for visitors, 403 otherwise. Use it in a page or action. |
refresh() | Fetches access again, and makes menus, tabs and widgets filter again. |
<script setup lang="ts">
const { can } = useAuthorization()
</script>
<template>
<UButton v-if="can('posts:update', post)" label="Edit post" />
</template>
- Where
accesscomes from. It is fetched during server rendering, so the first paint and the page middleware agree, kept inuseState, and fetched again when the signed-in user changes (sign-in, sign-out, switching accounts). canneeds the exact name. The list holds registered permissions, socan('events:*')is false: ask for the permission you mean.- Unregistered or unknown permissions are false. Typos fail closed.
- A policy that needs a record is only reflected when you pass it:
permissionsinaccessis what a check without a record allows.
<Can> and <Cannot>
<Can permission="events:update" :record="ev">
<button>Edit</button>
<template #fallback><span>Read only</span></template>
</Can>
<Cannot permission="admin:access">You are not an administrator.</Cannot>
<Can> renders its default slot when can(permission, record) is true, else the fallback slot. <Cannot> is the
opposite. permission is required; record is optional.
Route rules
Rules are keys on Nuxt's native routeRules.
There are three:
export default defineNuxtConfig({
routeRules: {
'/admin/**': { permission: 'admin:access' }, // pages
'/api/admin/**': { permission: 'admin:access' }, // the staff API of every module
'/admin/login': { authenticated: false }, // opens a sub-route of a protected pattern
'/members/**': { authenticated: true },
'/pricing': { guest: true }, // signed-out visitors only
},
})
| Key | Meaning |
|---|---|
permission | A registered permission the user must hold. 401 for visitors, 403 without it. An unknown permission fails the build. |
authenticated | true requires a signed-in user. false turns the rules of less specific patterns off for the path. |
guest | Only for signed-out visitors. A signed-in user gets 403. |
- Patterns follow route rules: a
/admin/**prefix,*for one segment, or an exact path. Every matching pattern contributes, and keys of more specific patterns win. - Who enforces them. Auth's server middleware enforces the rules for
/api/**, so they fail closed even without Authorization. Authorization adds the page middleware, which also runs during server-side rendering. A tampered client can still reach the API only as far as the API allows. - The server middleware runs
authorize, so tokens,sessionOnlypermissions and the before hook apply there exactly as in a handler. A rule is coarse: the handler still authorizes its own permission and records. - The middleware matches the percent-decoded path the router dispatches on, and an API path with an encoded
/, a\or a dot segment is refused with400. - Only pages and
/api/**are enforced. A rule for another server route (/mcp,/oauth/**) matches no page and is not enforced: the build warns about it. Guard such routes in their handler withauthorize. - Nuxt's app-side route rules matcher keeps only a fixed set of keys, so the module collects these keys from
routeRulesandnitro.routeRulesat build time. Rules added at runtime are not picked up. - Rules named
roles,abilityandscopesare not rules of this release. See Migrate access control.
Sign-in paths
Auth's anonymous entry points are always reachable, so '/**': { authenticated: true } does not lock users out of
signing in. They come from runtimeConfig.public.authentication.publicPaths
(Auth reference).
- An exempt pattern resets the rule: guards of less specific patterns stop there.
- A rule for the same or a more specific pattern still applies.
- With
@nuxtjs/i18nthe page patterns are exempt under every locale prefix too (/da/login). API paths are not prefixed.
Page meta
definePageMeta({ authorize }) replaces the matching route rules for one page:
<script setup lang="ts">
definePageMeta({ authorize: 'events:update' })
// or a rule: { permission: 'events:update' } | { authenticated: true } | { guest: true }
</script>
definePageMeta({ authorize: false }) opens the page, whatever the route rules say. A page that names an unregistered
permission fails the build (checked when pages resolve).
interface RouteRule {
permission?: string
authenticated?: boolean
guest?: boolean
}
type AuthorizeMeta = string | RouteRule | false
A visitor who fails a permission or authenticated rule is sent to redirectTo with the page in ?redirect=; with
no redirectTo, they see a 401 error. A signed-in user without the permission sees 403.
Menus, tabs and widgets
Menu items (Menu), widgets and tabs (Widget) and account menu
items take a permission. Authorization hides the ones the viewer does not hold, children included, through the
menus:filter, widgets:filter and tabs:filter hooks. The menu cache key includes an access version, so menus filter
again on sign-in and sign-out.
{ label: 'Reports', to: '/admin/reports', permission: 'reports:read' }
It only hides what is shown. The route behind an item, and the data behind a widget or tab, still check the permission themselves.
Client mode
With authentication.mode: 'client' (see Auth client mode), the module runs in
the app only. This is how @frigear-nu/mobile apps and any single-page app against an
OAuth Server issuer work.
- The route middleware,
useAuthorization,<Can>and<Cannot>work as in server mode. - There is no server side: no tables, no role assignment, no roles admin.
- Where access comes from.
useAuthorization()calls the issuer'sGET /api/auth/me/accesswith the bearer access token. When the issuer has no such endpoint (a404, or offline, for example the external frigear.nu), it falls back to the access token's frozenpermissionsclaim, decoded without verification.rolesis only present when the issuer answers. - The user is the ID token's claims mapping. Roles are not read from the token, so a
roleclaim has no effect. - Shared policies work the same, because the file is framework free: import it in the app as on the server.
- A guest redirects to
redirectTo, which defaults to Auth's sign-in path.
Mobile apps use exactly this: can('tickets:read') decides which tabs to show, and the
API the app calls enforces it.