Skip to main content
Nuxt Secure uses a profile-based RBAC model. Every user belongs to exactly one profile. Each profile holds a set of permissions, one row per application module. Each permission row defines five independent boolean action flags. This means access is controlled at two levels: which module and which action within that module.

The five permission actions

The PermisosAccion interface in useAuth.ts defines the five actions that can be granted or denied per module:
Action flagDescription
bitConsultaView / list records
bitAgregarCreate new records
bitEditarEdit existing records
bitEliminarDelete records
bitDetalleOpen the detail view of a record
All flags default to false in the database. A flag must be explicitly set to true for the action to be permitted.

Data model

The permisos_perfil table stores one row per profile-module combination:
ColumnTypeDescription
idserial PKAuto-increment primary key
idModulointeger FKReferences modulo.id
idPerfilinteger FKReferences perfil.id
bitAgregarbooleanCreate permission (default false)
bitEditarbooleanEdit permission (default false)
bitConsultabooleanView permission (default false)
bitEliminarbooleanDelete permission (default false)
bitDetallebooleanDetail permission (default false)

Permission loading

After a successful login, cargarMisPermisos(idPerfil) fetches the current user’s permission set from the server:
app/composables/useAuth.ts
const cargarMisPermisos = async (idPerfil: number) => {
  try {
    const response = await fetch(`/api/permisos/mis-permisos/${idPerfil}`)
    const data = await response.json()
    if (data.success) {
      misPermisos.value = data.permisos
    }
  } catch (error) {
    console.error("Error cargando la matriz de permisos:", error)
  }
}
The API endpoint returns a Record<string, PermisosAccion> where each key is the module name in uppercase (e.g. "USUARIO", "PERMISOS-PERFIL"). This object is stored in useState('misPermisos') and is available reactively across the entire app. restaurarSesion() calls cargarMisPermisos again after a hard refresh if the in-memory state is empty, so permissions survive F5.

Checking permissions at runtime

tienePermiso() is the single function used everywhere in the UI to check whether the current user can perform an action:
app/composables/useAuth.ts
const tienePermiso = (nombreModulo: string, accion: keyof PermisosAccion) => {
  const modulo = misPermisos.value[nombreModulo.toUpperCase()]
  return modulo ? modulo[accion] : false
}
If the module key is not present in misPermisos (because no permission row exists for this profile-module pair), the function returns false — deny by default.

Usage in pages

Use tienePermiso with v-if to conditionally render UI elements based on the current user’s permissions:
app/pages/seguridad/usuario.vue
<script setup>
const { tienePermiso } = useAuth()
const router = useRouter()

onMounted(() => {
  if (!tienePermiso('USUARIO', 'bitConsulta')) {
    router.replace('/')
  }
})
</script>

<template>
  <!-- Show the create button only if the user can view AND add records -->
  <button v-if="tienePermiso('USUARIO', 'bitConsulta') && tienePermiso('USUARIO', 'bitAgregar')">
    New user
  </button>

  <!-- Show edit controls only if the user can edit -->
  <button v-if="tienePermiso('USUARIO', 'bitEditar')">Edit</button>

  <!-- Show delete controls only if the user can delete -->
  <button v-if="tienePermiso('USUARIO', 'bitEliminar')">Delete</button>

  <!-- Show detail controls only if the user can view details -->
  <button v-if="tienePermiso('USUARIO', 'bitDetalle')">View</button>
</template>

Route protection

Each security page checks bitConsulta in its onMounted hook. If the user lacks view permission, they are redirected to / before the page renders:
onMounted(() => {
  if (!tienePermiso('USUARIO', 'bitConsulta')) {
    router.replace('/')
  }
})
This check is client-side only. API routes have their own server-side validation and return HTTP 401/403 for unauthorised requests regardless of the UI state.

Permissions matrix

Administrators configure permissions through the visual permissions matrix UI. Selecting a profile loads all modules as rows, each with five toggleable checkboxes. Saving posts the entire matrix to /api/permisos/guardar-matriz in a single request. If the profile being edited belongs to the currently logged-in user, cargarMisPermisos is called immediately after saving so the session reflects the changes without a logout. See Permissions matrix for the full walkthrough.

Example permissions layout

ModulebitConsultabitAgregarbitEditarbitEliminarbitDetalle
USUARIO
PERMISOS-PERFIL
Module names in tienePermiso() are compared in uppercase, so tienePermiso('usuario', 'bitConsulta') and tienePermiso('USUARIO', 'bitConsulta') are equivalent.

Build docs developers (and LLMs) love