Skip to main content

Descripción General

El sistema implementa autorización basada en permisos granulares asociados a roles. Los permisos siguen el formato recurso.accion y se verifican mediante middleware en cada endpoint protegido.

Envío de Tokens

Todos los endpoints protegidos requieren el token de autenticación en el header Authorization.

Formato del Header

Authorization: Bearer {token}

Ejemplo de Petición

curl -X GET https://api.example.com/api/productos \
  -H "Authorization: Bearer 1|abcdefghijklmnopqrstuvwxyz1234567890" \
  -H "Content-Type: application/json"
Token en Query String: Para descargas de PDFs y exportaciones que se abren en el navegador, el sistema también acepta el token como parámetro de query ?token= mediante el middleware TokenFromQuery. Esto se aplica únicamente a rutas web específicas.
https://api.example.com/reporteNV/12345?token=1|abcdefghijklmnopqrstuvwxyz1234567890

Middleware de Permisos

El middleware CheckPermission se aplica a rutas que requieren permisos específicos.

Definición en Rutas

// En routes/api.php

// Requiere permiso "productos.view"
Route::get('productos', [ProductoController::class, 'index'])
    ->middleware('permission:productos.view');

// Requiere permiso "productos.create"
Route::post('productos', [ProductoController::class, 'store'])
    ->middleware('permission:productos.create');

// Requiere permiso "ventas.edit"
Route::put('ventas/{id}', [VentasController::class, 'update'])
    ->middleware('permission:ventas.edit');

Lógica del Middleware

El middleware CheckPermission implementa la siguiente lógica:
public function handle(Request $request, Closure $next, string $permission): Response
{
    $user = $request->user();

    // 1. Verificar autenticación
    if (!$user) {
        return response()->json([
            'success' => false,
            'message' => 'No autenticado'
        ], 401);
    }

    // 2. Admin bypass - rol_id = 1 tiene acceso total
    if ($user->rol_id == 1) {
        return $next($request);
    }

    // 3. Verificar permiso específico del rol
    if (!$user->hasPermission($permission)) {
        return response()->json([
            'success' => false,
            'message' => 'No tienes permiso para realizar esta acción'
        ], 403);
    }

    return $next($request);
}

Formato de Permisos

Los permisos siguen la convención recurso.accion:

Estructura

recurso
string
Nombre del recurso o módulo (productos, ventas, clientes, etc.)
accion
string
Acción permitida sobre el recurso (view, create, edit, delete)

Permisos Comunes

productos.view
permission
Ver listado y detalles de productos
productos.create
permission
Crear nuevos productos
productos.edit
permission
Editar productos existentes
productos.delete
permission
Eliminar productos
ventas.view
permission
Ver listado y detalles de ventas
ventas.create
permission
Crear nuevas ventas
ventas.edit
permission
Editar ventas existentes
ventas.delete
permission
Anular ventas
clientes.view
permission
Ver listado y detalles de clientes
clientes.create
permission
Crear nuevos clientes
clientes.edit
permission
Editar clientes existentes
clientes.delete
permission
Eliminar clientes
proveedores.view
permission
Ver listado y detalles de proveedores
proveedores.create
permission
Crear nuevos proveedores
proveedores.edit
permission
Editar proveedores existentes
proveedores.delete
permission
Eliminar proveedores
compras.view
permission
Ver listado y detalles de compras
compras.create
permission
Registrar nuevas compras
compras.edit
permission
Editar compras existentes
compras.delete
permission
Anular compras
cotizaciones.view
permission
Ver listado y detalles de cotizaciones
cotizaciones.create
permission
Crear nuevas cotizaciones
cotizaciones.edit
permission
Editar cotizaciones existentes
cotizaciones.delete
permission
Eliminar cotizaciones

Acceso Basado en Roles

Los permisos se asignan a roles, no directamente a usuarios.

Estructura de Roles

Cada usuario tiene un rol_id que determina sus permisos:
{
  "user": {
    "id": 15,
    "name": "Juan Pérez",
    "email": "[email protected]",
    "rol_id": 3,
    "id_empresa": 5
  }
}

Carga de Permisos en Login

Durante el login, el sistema carga automáticamente los permisos del rol:
{
  "success": true,
  "token": "1|abc123...",
  "user": { ... },
  "permissions": [
    "productos.view",
    "productos.create",
    "ventas.view",
    "ventas.create",
    "clientes.view"
  ]
}
Permisos del Cliente: El frontend debe usar esta lista para mostrar/ocultar opciones de UI. Sin embargo, la autorización real siempre se valida en el servidor.

Bypass de Administrador

Los usuarios con rol_id = 1 tienen privilegios especiales:

Características del Rol Admin

Acceso Total
feature
Los administradores bypasean todas las verificaciones de permisos en el middleware CheckPermission.
Todos los Permisos
feature
Durante el login, los administradores reciben automáticamente la lista completa de permisos disponibles en el sistema.
Multi-Empresa
feature
Los administradores pueden ver y cambiar entre todas las empresas activas mediante el endpoint /api/switch-empresa.
Sin Restricciones
feature
No requieren permisos explícitos asignados a su rol. El código verifica primero si rol_id == 1 antes de consultar permisos.

Ejemplo de Verificación

// En AuthController::login()

if ($user->rol_id == 1) {
    // Admin: todas las empresas
    $empresas = Empresa::where('estado', '1')->get();
    
    // Admin: todos los permisos
    $permissions = Permission::pluck('name')->toArray();
} else {
    // Usuario normal: solo su empresa
    $empresas = [$user->empresa];
    
    // Usuario normal: solo permisos de su rol
    $permissions = $user->rol->permissions->pluck('name')->toArray();
}

Respuestas de Error

Error 401 - No Autenticado

Cuando la petición no incluye un token válido:
{
  "success": false,
  "message": "No autenticado"
}
Causas comunes:
  • Token no incluido en el header Authorization
  • Token expirado (más de 8 horas)
  • Token revocado (después de logout)
  • Token con formato incorrecto

Error 403 - Sin Permisos

Cuando el usuario no tiene el permiso requerido:
{
  "success": false,
  "message": "No tienes permiso para realizar esta acción"
}
Causas comunes:
  • El rol del usuario no tiene el permiso específico asignado
  • Se intenta acceder a un recurso de otra empresa (en sistemas multi-empresa)
curl -X POST https://api.example.com/api/productos \
  -H "Authorization: Bearer 1|validtoken123" \
  -H "Content-Type: application/json" \
  -d '{
    "nombre": "Producto Nuevo",
    "precio": 100
  }'

# Respuesta: 403 Forbidden
# El usuario tiene token válido pero su rol no incluye "productos.create"

Endpoints de Gestión de Permisos

El sistema incluye endpoints para administrar permisos:

Listar Todos los Permisos

GET /api/permissions
Retorna todos los permisos disponibles en el sistema.

Obtener Permisos del Usuario Actual

GET /api/permissions/user
Retorna los permisos del usuario autenticado.

Obtener Permisos de un Rol

GET /api/permissions/role/{rolId}
Retorna los permisos asignados a un rol específico.

Actualizar Permisos de un Rol

PUT /api/permissions/role/{rolId}
Actualiza los permisos asignados a un rol.
curl -X PUT https://api.example.com/api/permissions/role/3 \
  -H "Authorization: Bearer 1|admintoken123" \
  -H "Content-Type: application/json" \
  -d '{
    "permissions": [
      "productos.view",
      "productos.create",
      "ventas.view",
      "clientes.view"
    ]
  }'
Solo Administradores: Los endpoints de gestión de permisos típicamente están restringidos a usuarios administradores.

Buenas Prácticas

1. Almacenamiento Seguro del Token

// ✅ Correcto - localStorage
localStorage.setItem('auth_token', token);

// ❌ Evitar - variables globales
window.authToken = token;

2. Interceptores de Axios

Configura interceptores para incluir el token automáticamente:
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com/api'
});

// Request interceptor - añade el token
api.interceptors.request.use(
  config => {
    const token = localStorage.getItem('auth_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

// Response interceptor - maneja errores 401
api.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.status === 401) {
      // Token expirado o inválido
      localStorage.removeItem('auth_token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default api;

3. Verificación de Permisos en el Frontend

// Verificar permiso antes de mostrar botón
const permissions = JSON.parse(localStorage.getItem('permissions') || '[]');
const canCreateProducts = permissions.includes('productos.create');

return (
  <div>
    {canCreateProducts && (
      <button onClick={handleCreateProduct}>
        Crear Producto
      </button>
    )}
  </div>
);
Seguridad del Cliente: La verificación de permisos en el frontend es solo para mejorar UX. La autorización real siempre ocurre en el servidor.

4. Manejo de Sesión Expirada

// Verificar token antes de peticiones críticas
const checkTokenValidity = async () => {
  try {
    await api.get('/verify');
    return true;
  } catch (error) {
    if (error.response?.status === 401) {
      // Token inválido - redirigir a login
      localStorage.removeItem('auth_token');
      window.location.href = '/login';
      return false;
    }
  }
};

// Uso
if (await checkTokenValidity()) {
  // Continuar con la operación
}

5. Refresh Automático de Token

// Refrescar token cada 7 horas (antes de las 8 horas de expiración)
setInterval(async () => {
  try {
    const response = await api.post('/refresh');
    localStorage.setItem('auth_token', response.data.token);
  } catch (error) {
    console.error('Error al refrescar token:', error);
  }
}, 7 * 60 * 60 * 1000); // 7 horas en milisegundos

Build docs developers (and LLMs) love