Install nuxt-authorization
mainTo add the module to your Nuxt application, use the following command:
npx nuxi module add nuxt-authorizationrepository·main·Indexed 18 days ago
https://github.com/barbapapazes/nuxt-authorizationAn authentication-agnostic authorization module for Nuxt and Nitro server applications (version 0.3.5). It provides low-level primitives to manage permissions via client and server-side resolvers, define abilities with defineAbility, and enforce access using allows(), denies(), and authorize() functions. Includes Vue components <Can>, <Cannot>, and <Bouncer> for conditional UI rendering based on user permissions.
To add the module to your Nuxt application, use the following command:
npx nuxi module add nuxt-authorizationThe module is authentication-agnostic and requires you to implement two resolvers to retrieve the user on the client and the server.
Important: Resolver functions should return a stored user rather than fetching it from a database every time to avoid performance issues. Use a plugin to fetch the user once at startup and store it in a state/session.
Create a plugin in plugins/authorization-resolver.ts to provide resolveClientUser. This function should return the user object or null if unauthenticated.
Create a plugin in server/plugins/authorization-resolver.ts using defineNitroPlugin. Use the request hook to attach resolveServerUser to event.context.$authorization.
// plugins/authorization-resolver.ts
export default defineNuxtPlugin({
name: 'authorization-resolver',
parallel: true,
setup() {
return {
provide: {
authorization: {
resolveClientUser: () => {
// Your logic to retrieve the user from the client
},
},
},
}
},
})// server/plugins/authorization-resolver.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('request', async (event) => {
event.context.$authorization = {
resolveServerUser: () => {
// Your logic to retrieve the user from the server
},
}
})
})An authorizer function can return several types of responses to indicate authorization status:
boolean: A simple true (authorized) or false (unauthorized).AuthorizationResponse: An object providing more context, such as a custom statusCode and message to be returned when authorization fails.Promise<boolean | AuthorizationResponse>: An asynchronous version of the above, allowing for database lookups or external API calls during authorization checks.export type AuthorizationResponse = {
statusCode?: number
message?: string
authorized: boolean
}
export type AuthorizerResponse
= | boolean
| AuthorizationResponse
| Promise<boolean | AuthorizationResponse>Use these components to show or hide parts of your Vue templates based on user abilities.
<Can>: Renders content only if the user can perform the ability.<Cannot>: Renders content only if the user cannot perform the ability.<Bouncer>: A unified component using named slots (#can and #cannot) to handle both states.:ability: The ability function (or an array of abilities).:args: An array of arguments to pass to the ability function. If using multiple abilities, provide an array of argument arrays.as: (Optional) The HTML tag to render. Defaults to a renderless component.When passing an array of abilities, the component renders only if all abilities match.
<!-- Using Can -->
<Can :ability="editPost" :args="[post]" as="div">
<button>Edit</button>
</Can>
<!-- Using Cannot -->
<Cannot :ability="editPost" :args="[post]">
<p>You're not allowed to edit.</p>
</Cannot>
<!-- Using Bouncer with slots -->
<Bouncer :ability="editPost" :args="[post]">
<template #can>
<button>Edit</button>
</template>
<template #cannot>
<p>You're not allowed to edit.</p>
</template>
</Bouncer>
<!-- Multiple abilities (All must match) -->
<Can :ability="[editPost, deletePost]" :args="[[post], [post]]" />An ability is a function that takes a user (and optional arguments) and returns a boolean indicating if the action is permitted. It is recommended to define these in a shared directory (e.g., shared/utils/abilities.ts) so they are accessible to both client and server.
By default, guests (unauthenticated users) are denied all actions. You can change this by passing an options object to defineAbility with allowGuest: true.
You can use deny(message, statusCode) instead of returning false to return a custom error message and HTTP status code (e.g., returning a 404 instead of 403).
// Basic ability
export const listPosts = defineAbility(() => true)
// Ability with arguments
export const editPost = defineAbility((user: User, post: Post) => {
return user.id === post.authorId
})
// Allowing guests
export const listPosts = defineAbility({ allowGuest: true }, (user: User | null) => true)
// Custom error response
export const editPost = defineAbility((user: User, post: Post) => {
if(user.id === post.authorId) {
return true
}
return deny('This post does not exist', 404)
})The module provides three primary functions to check permissions. On the server, you must pass the event as the first argument.
// --- Client Side ---
// Check if user can perform action (returns boolean)
if (await allows(listPosts)) { /* ... */ }
// Check if user cannot perform action (returns boolean)
if (await denies(editPost, post)) { /* ... */ }
// Throw error if user cannot perform action
await authorize(editPost, post)
// --- Server Side ---
// Check if user can perform action (requires event)
if (await allows(event, listPosts)) { /* ... */ }
// Check if user cannot perform action (requires event)
if (await denies(event, editPost, post)) { /* ... */ }
// Throw error if user cannot perform action (requires event)
await authorize(event, editPost, post)Use authorize to enforce permissions. If the user does not have the required ability, this function will throw a Nuxt error. This is useful for protecting client-side logic or component lifecycle hooks where an unauthorized state should halt execution.
If the underlying authorization check fails with an AuthorizationError, authorize catches it and re-throws it using Nuxt's createError utility, preserving the original statusCode and message.
// Example: Enforcing permission before executing a sensitive action
try {
await authorize('user:delete', userId);
// If we reach here, the user is authorized
await performDelete(userId);
} catch (err) {
// err will be a Nuxt error with the appropriate statusCode
console.error('Unauthorized:', err.message);
}Use allows and denies within Nuxt server routes (H3 events) to perform non-blocking permission checks. These functions resolve the current server user from event.context.$authorization and return a boolean based on the provided ability and arguments.
allows(event, ability, ...args): Returns true if the user is permitted, false otherwise.denies(event, ability, ...args): Returns true if the user is forbidden, false otherwise.// Example usage in a server route
export default defineEventHandler(async (event) => {
const canEdit = await allows(event, 'edit_post', postId);
if (!canEdit) {
throw createError({ statusCode: 403, statusMessage: 'Forbidden' });
}
// Proceed with logic...
});Use authorize to enforce a permission check. If the user does not have the required ability, this function will throw a Nuxt-compatible error using createError.
If the underlying authorization logic fails with an AuthorizationError, authorize catches it and re-throws it as a standard Nuxt error, preserving the original statusCode and message. This is the preferred method for protecting server routes where unauthorized access should immediately halt execution.
// Example usage in a server route
export default defineEventHandler(async (event) => {
// This will throw a 403 (or appropriate status) if the user is not authorized
await authorize(event, 'delete_user', userId);
// If we reach here, the user is authorized
await performDelete(userId);
});The normalizeAuthorizationResponse utility converts different result types into a consistent AuthorizationResponse object. It accepts either a simple boolean or a full AuthorizationResponse object, ensuring the output always follows the { authorized: boolean, message?: string, statusCode?: number } shape.
// If input is boolean:
// normalizeAuthorizationResponse(true) -> { authorized: true }
// If input is object:
// normalizeAuthorizationResponse({ authorized: false, message: 'No access' }) -> { authorized: false, message: 'No access' }Use allows and denies to perform non-blocking permission checks in your client-side Nuxt application. These functions resolve the current client user automatically and return a boolean indicating whether the user has the specified ability with the provided arguments.
allows(ability, ...args): Returns true if the user is permitted to perform the action.denies(ability, ...args): Returns true if the user is explicitly forbidden from performing the action.// Example: Checking if a user can edit a specific post
const canEdit = await allows('post:edit', postId);
if (canEdit) {
// Proceed with logic
}
// Example: Checking if a user is denied access
const isDenied = await denies('admin:access');Use allows to verify if a user has permission to perform an action, returning a boolean. Use denies to verify the inverse (returning true if the user is unauthorized). Both functions execute the provided ability and normalize the response.
allows(ability, user, ...args): Returns true if authorized, false otherwise.denies(ability, user, ...args): Returns true if unauthorized, false otherwise.// Example usage of allows and denies
const canEdit = await allows(editPostAbility, currentUser, postId);
const isForbidden = await denies(adminOnlyAbility, currentUser);