---
contentId: f47ef865-e311-48e6-ab6e-6f7ee6bde6e8
title: "Nuxt Going Full-Stack: How to Handle Authorization?"
description: Secure your Nuxt app with advanced authorization techniques. Learn to restrict unauthorized access and safeguard sensitive data seamlessly.
date: 2024-06-09
---

When developing an application, it is essential to **grant** or **restrict** access to specific areas or data based on a user's role or permissions. This concept is known as **authorization**.

Authorization is a critical aspect of any application for **security** purposes. It is imperative to protect **sensitive data**, such as a user's email address or password, and prevent the disclosure of **private information** that could compromise user privacy and your business integrity.

To clarify: **authentication** involves verifying a user's identity, whereas **authorization** involves permitting or denying access to resources based on the user's role or permissions.

While building Orion, I confronted this challenge and experimented with various approaches. In this article, I'll share several strategies to manage authorization in a NuxtHub application, ranging from the simplest to the most sophisticated methods. It is crucial to understand that the most complex method is not necessarily the best for every application.

Orion serves as a community-driven collection of templates for your projects, from landing pages to complete web applications. NuxtHub functions as a deployment and management platform for Nuxt, powered by Cloudflare.

<CalloutReadMore id="c21a371e-abdd-4325-b296-dadcb8deec25" />

## The context

Consider this **endpoint** in your Nuxt application:

```ts
export default defineEventHandler(async (event) => {
  const id = getRouteParam(event, 'id')

  const publication = await db.query.publications.findOne({
    where: { id },
    columns: {
      title: true,
      content: true,
      status: true,
      authorId: true,
    },
  })

  return publication
})
```

You can access this endpoint by making a `GET /api/publication/<id>` request. This endpoint returns a publication. At first glance, there is no issue with this endpoint. However, a closer examination reveals that a publication has a status that can be `draft`, `published`, or `deleted`.

The concern is that anyone can access this endpoint and view a **draft publication** or a **deleted publication**. Some might be able to view the content of a publication before it is published, which could contain vital information such as a press release or a product launch. Such a scenario could significantly harm your business.

This is a security issue that necessitates restricting access to this endpoint, utilizing the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege). Security is not merely a feature but should be ingrained in the system's design.

## Authenticated or not

The following section is premised on the utilization of the Nuxt [**nuxt-auth-utils**](https://github.com/Atinux/nuxt-auth-utils) package. Nevertheless, its usage is not essential.

The initial step in securing this **endpoint** is verifying if the user is **authenticated**. If the user lacks authentication, it should return a `401 Unauthorized` error.

Upon exploring the **nuxt-auth-utils** package, I discovered a server utility termed `requireUserSession`. This utility can be employed at an endpoint's inception to attempt retrieving the user's session and triggering a `401 Unauthorized` error if there is no session, indicating the user is unauthenticated.

```ts
export default defineEventHandler(async (event) => {
  await requireUserSession(event)

  const publication = {} // ...

  return publication
})
```

This is the preliminary step towards safeguarding the endpoint against **unauthenticated users**. However, if anyone can register an account, they may still access the content of a draft publication. Further action is necessary.

## Only admins

Building on `requireUserSession`, I can readily devise a new utility named `requireAdminSession`. This session will verify if the user is authenticated and has admin rights. If the user is not an admin, a `403 Forbidden` error should be returned. Unauthorized relates to authentication, while Forbidden pertains to authorization.

```ts
export async function requireAdminSession(event: H3Event, opts: { statusCode?: number, message?: string } = {}): Promise<UserSessionRequired> {
  const userSession = await requireUserSession(event)
  const { user } = userSession

  if (user.roleType !== 'admin') {
    throw createError({
      statusCode: opts.statusCode || 403,
      message: opts.message || 'Unauthorized',
    })
  }

  return userSession as UserSessionRequired
}
```

Subsequently, `requireUserSession` can be replaced with `requireAdminSession` in the endpoint.

```ts
export default defineEventHandler(async (event) => {
  await requireAdminSession(event)

  const publication = {} // ...

  return publication
})
```

Now, only **admins**, users assigned the `admin` role, can access a publication's content.

However, this approach is not entirely effective, as if the publication is published, I wish to permit all users to access it. Currently, only admins can view the content of a publication, irrespective of the publication's status.

## Issues with these approaches

The aforementioned strategies lack sufficient flexibility for a detailed **authorization system**. They cannot accommodate situations where the author of the publication should have access, even if the publication is unpublished.

To address this, I could draft another utility termed `requirePublicationAccess`:

```ts
export async function requirePublicationAccess(event: H3Event, publication: Publication, opts: { statusCode?: number, message?: string } = {}): Promise<UserSessionRequired> {
  const userSession = await getUserSession(event)
  const { user } = userSession

  if (publication.status === 'published')
    return userSession

  if (!userSession) {
    throw createError({
      statusCode: opts.statusCode || 401,
      message: opts.message || 'Unauthorized',
    })
  }

  if (publication.authorId === user.id)
    return userSession

  if (user.roleType === 'admin')
    return userSession

  throw createError({
    statusCode: opts.statusCode || 403,
    message: opts.message || 'Forbidden',
  })
}
```

This solution is adequate until I need to manage additional endpoints. This approach will result in excessive code duplication concerning error handling. Additionally, the code becomes more challenging to test due to the necessity of managing an entire H3 event.

Without a superior approach, I continue developing Orion but soon encountered another challenge: I need to implement the same **authorization logic on the client side**. I do not want to show an edit button to a user who is neither the author of the publication nor an admin, and I do not wish to hide this edit button from the admin if I decide to permit publication editing on the server. Similar to [forms validation](./nuxt-going-full-stack-how-to-streamline-form-validation.md), duplicating logic can result in substantial inconsistencies between the client and the server, leading to customer frustration. The utilities I have created thus far are solely server-side due to the use of `requireUserSession` and `getUserSession`.

Let's take a step back to comprehend how this issue might be resolved. Authorization consists of three components:

- **allow** access to a resource
- **deny** access to a resource
- **authorize** access to a resource

The first two are straightforward conditions yielding a boolean response to questions like "Can I access this publication? Yes or No". The developer must manually handle this response. The third one grants access to the resource. For the question "Can I access this publication?", there is neither a "yes" nor "no"; nothing happens if the answer is "yes", but an error is raised if it is "no". The `authorize` sends an error automatically.

These elements hold no direct relation to the client, server, or the framework in use or the authentication system. Armed with this knowledge, I began working on a local module for Orion to manage authorization more flexibly. After several days of effort, I am proud to introduce **nuxt-authorization**.

Initially, I aimed to develop both a Nitro and a Nuxt package, but Nitro modules are not ready yet. Nonetheless, I submitted some pull requests to the Nitro repository to enhance the module system. One of these is the [module author guide](https://github.com/unjs/nitro/pull/2508).

## Introducing nuxt-authorization

This module offers a simple yet powerful method for managing **authorization** in a Nuxt application, applicable to both client and server. It is authentication system agnostic but can be seamlessly integrated with [**nuxt-auth-utils**](https://github.com/Atinux/nuxt-auth-utils).

For instructions on its usage, visit the GitHub repository: <GitHubLink repo="barbapapazes/nuxt-authorization" />. To delve into the design and implementation, continue reading.

The module remains in its early stages of development, and if you have any feedback or suggestions for improvement, feel free to open an issue on the GitHub repository or contact me on [X](https://x.com/barbapapazes).

### Problems I want to solve

Before describing what has been accomplished with **nuxt-authorization**, let's examine the problems I intended to solve.

Much like forms validation, I aim to maintain a single source of truth and a single API for both the app and server. This is crucial to avoid discrepancies between the app and server.

I also seek the ability to define **authorization logic** simply and grouped by resource. This approach enhances readability, maintainability, and consistency across various application aspects, such as forms validation.

The authorization logic must be adaptable enough to accommodate a varied range of use cases, from `allow` or `deny` conditions to more complex `authorize` conditions. The errors produced by the `authorize` condition should be customizable to enhance user experience. Not all unauthorized errors need to be a `403 Forbidden`.

Finally, I desire components that ease integrating authorization logic into templates. For instance, I want the ability to show or hide a button based on authorization logic.

### Design

The module comprises two components: the **ability** and the **bouncer**.

Abilities constitute the rules that embody the **authorization logic**. They take a user and one or more resources and yield a deny or an allow condition. While they can be grouped by resource, they remain independent of one another.

```ts
export const editBook = defineAbility((user: User, book: Book) => {
  return user.id === book.authorId
})
```

In the above example, only the author of a book is allowed to edit it. By default, abilities are executed only if the user is authenticated, but it is possible to permit guest access to certain resources.

```ts
export const listBooks = defineAbility((user: User | null) => {
  return true
})
```

In this instance, everyone, including unauthenticated users, is permitted to list books.

The `defineAbility` function acts as a factory that creates an ability but nothing beyond that. It needs to be used with bouncer functions.

In a server endpoint, the `authorize` function can be employed to grant access to a resource based on the abilities.

```ts
export default defineEventHandler(async (event) => {
  await authorize(event, listBooks)

  const books = await db.query.books.findMany()

  return books
})
```

The `authorize` function will trigger a `403 Forbidden` error if the user is not permitted to list books. More granular control can be achieved using the `allows` or `denies` functions.

```ts
export default defineEventHandler(async (event) => {
  if (await denies(event, listBooks, book))
    return []

  const books = await db.query.books.findMany()

  return books
})
```

Instead of triggering an error, an empty array is returned if the user is not allowed to list books. The appropriate method for handling unauthorized access will depend on your business logic.

For example, returning a `404 Not Found` error if the user is not authorized to access a specific resource can preserve data privacy. Disclosing the existence of the resource could pose a security risk.

This customization is attainable within the abilities through the `allow` and `deny` functions.

```ts
export const viewBook = defineAbility((user: User, book: Book) => {
  if (book.status === 'draft') {
    return deny({
      statusCode: 404,
      message: 'Not Found',
    })
  }

  return allow()
})
```

Now, the `authorize` function will throw a `404 Not Found` error if the user is not permitted to view a draft book, rather than the default `403 Forbidden`.

On the client side, the `allows`, `denies`, and `authorize` functions can also be utilized to administer the **authorization logic**.

In addition, these functions offer two components: `Can` and `Cannot`. These components enable portions of a template to be shown or hidden based on the **authorization logic**.

```vue
<template>
  <Can :ability="editBook" :args="[book]">
    <button>Edit</button>
  </Can>
</template>
```

Consult the GitHub repository for more information: <GitHubLink repo="barbapapazes/nuxt-authorization" />.

### Final thoughts

Both the code and the design of this package draw significant inspiration from the [Adonis Bouncer](https://docs.adonisjs.com/guides/security/authorization). It is well-crafted, and reinventing concepts unnecessarily is often futile.

I am already implementing this package in Orion, and the experience has been quite enjoyable. I am confident there is still potential for enhancement, but this module represents an initial step in empowering Nuxt developers and advancing Nuxt's full-stack capabilities. Contributions and suggestions for new features or improvements are welcome.

Orion is a community-driven collection of templates for your projects, spanning from landing pages to comprehensive web applications.

I hope this article and module assist you in managing **authorization** within your Nuxt application, thereby preventing and restricting **unauthorized access** to your data. Security is imperative.

Happy coding!
