Tutorialยท

Implementing Public Permissions in Nuxt Auto CRUD

A guide on how to expose and consume public permissions safely using Nuxt Auto CRUD.

When building a secured application, you often need to expose certain parts of your API to the public while keeping others locked down. In Nuxt Auto CRUD, we handle this through a robust Role-Based Access Control (RBAC) system.

However, your frontend application needs to know what it is allowed to do, even for a guest user who hasn't logged in yet. This is where the /api/public-permissions endpoint comes in.

The Challenge

By default, your front-end components (like buttons, forms, or navigation items) might be hidden or disabled based on user roles (like admin or manager).

But what about a public visitor?

  • Can they see the "Testimonial" submission form?
  • Can they read the list of "Products"?

If your abilities logic only checks user.permissions, a guest user (where user is null) will fail every check, leading to a broken or completely empty UI.

The Solution: Public Permissions API

We solved this by explicitly fetching the permissions granted to the public role and storing them in a way that our frontend authorization logic can access.

1. The API Endpoint

We created a simple server endpoint that fetches all permissions associated with the public role from the database.

// server/api/public-permissions.get.ts
export default eventHandler(async () => {
  return await getPublicPermissions()
})

This returns a JSON object mapped by resource:

{
  "testimonials": ["create", "read", "list"],
  "products": ["read", "list"]
}

2. Consuming in abilities.ts

Our abilities.ts file acts as the bridge. It combines three checks:

  1. Admin Check: Is the user an admin? (Allow everything)
  2. User Check: Does the logged-in user have specific DB permissions?
  3. Public Fallback: If neither of the above, check our cached public permissions.

Here is the simplified logic:

// shared/utils/abilities.ts
let publicPermissionsCache = null

export const abilityLogic = async (user, model, action) => {
  // 1. Admin Override
  if (user?.role === 'admin') return true

  // 2. Logged-in User Permissions
  if (user?.permissions?.[model]?.includes(action)) return true

  // 3. Fallback to Public Permissions
  // Fetch only once and cache it on the client side
  if (!publicPermissionsCache) {
    publicPermissionsCache = await $fetch('/api/public-permissions')
      .catch(() => ({}))
  }
  
  return publicPermissionsCache[model]?.includes(action) || false
}

Why Fetch from API?

You might ask, "Why not just hardcode the public permissions in the frontend?"

Dynamic & Database-Driven: By fetching them from the API, your public permissions remain defined in your database (specifically, the permissions table linked to the public role). This means you can change what guests can do (e.g., turn off public registration or comments) via your Admin Dashboard without deploying new code.

Conclusion

This pattern ensures your frontend is smart enough to handle guest users gracefully. By adhering to the single source of truth (your database permissions), you keep your app secure and flexible.