Documentación técnica

OAuth2 Identity Server

Manual de uso: puesta en marcha, panel de administración, flujos OAuth2 y API REST completa para construir cualquier cliente propio (Angular, Flutter, NestJS, etc.).

Laravel 12 · Passport 13 · Vue 3 + TypeScript · SCSS/BEM · spatie/laravel-permission · L5-Swagger
Generado el 25 de julio de 2026

Índice

1. Qué es esta app

Es un servidor de identidad / OAuth2 (Laravel Passport) pensado para ser el centro único de usuarios, autenticación y permisos de un conjunto de aplicaciones propias. No es una app de negocio con datos propios: su único "recurso" son los usuarios y sus roles.

Provee tres piezas:

Idea central: cada aplicación externa que quiera loguear usuarios se registra acá como un cliente OAuth ("app"). Un mismo usuario puede tener un rol global (admin/user) y, además, un rol distinto dentro de cada app (por ejemplo: admin en la app de Angular, usuario normal en la de NestJS).
Dos frentes, misma API subyacente: la SPA Vue (sesión + cookies, pensada para uso interno en el navegador de este mismo servidor) y la API REST (Bearer token, pensada para cualquier cliente externo) exponen las mismas capacidades. Elegí el que corresponda: sesión para la UI que ya viene incluida, token para construir la tuya propia.

2. Puesta en marcha

Requisitos

Instalación (primera vez)

composer install
npm install
php artisan migrate --seed
npm run build
Frontend: TypeScript (npm run type-check corre vue-tsc sin emitir archivos) y SCSS compilado por Vite (paquete sass) desde resources/scss/app.scss — no hay ningún framework de CSS utility-first instalado.

Levantar todo en desarrollo

composer run dev

Este comando levanta en paralelo: el servidor PHP (php artisan serve), Vite en modo watch, la cola de trabajos y los logs. La app queda disponible en http://localhost:8000.

Reiniciar todo de cero: php artisan migrate:fresh --seed borra la base y vuelve a crear los usuarios/clientes de ejemplo descriptos en la sección 3. Los client_secret de los clientes OAuth de ejemplo se imprimen en la consola en ese momento — solo se muestran una vez.

3. Usuarios por defecto (seed)

El seeder (database/seeders/DatabaseSeeder.php) crea una cuenta admin y algunos usuarios de ejemplo para poder probar la app de inmediato:

🛡 Administrador

Email: admin@example.com
Password: password
Rol global: admin
Acceso al panel de administración de usuarios.

👤 Usuario normal

Email: carla@example.com
Password: password
Rol global: user
Sin acceso al panel admin. Es admin dentro de la app "Demo SPA" (rol por-app de ejemplo). Junto con martin@example.com y bea@example.com, solo están para que la sección de analíticas tenga datos de entrada.
El seeder también crea nuevo@example.com / password sin aprobar, para que la pestaña "Pendientes" del panel de usuarios (sección 4) no arranque vacía.
Instalación sin seed (producción): si nunca corriste db:seed, no hay ningún usuario en la base. En ese caso, la primera cuenta que se registre por /register queda automáticamente como admin, aprobada y con la sesión iniciada — no hace falta que nadie más la apruebe, porque todavía no existe ningún admin que pudiera hacerlo. La pantalla de registro avisa de esto con un mensaje cuando detecta que la base está vacía. A partir del segundo registro, vuelve a regir el flujo normal (pendiente de aprobación por un admin).

Clientes OAuth (apps) de ejemplo

El seeder también registra apps de prueba, una por cada flujo soportado. El client_id es fijo por ejecución (UUID nuevo cada migrate:fresh); el client_secret solo se muestra por consola al sembrar — si lo perdiste, hay que crear un cliente nuevo desde el panel (sección 4) o volver a sembrar.

NombreGrant typeUso
Demo SPA (Authorization Code + PKCE)authorization_codeCliente público (sin secret), para apps tipo SPA/móvil.
Demo Service (Client Credentials)client_credentialsComunicación máquina-a-máquina, sin usuario.
Demo First-Party App (Password Grant)passwordApps propias de confianza, login directo usuario/contraseña.
Swagger UIauthorization_code, client_credentialsPara probar el botón "Authorize" de la documentación interactiva.
Personal Access Clientpersonal_accessInterno — necesario para poder emitir Personal Access Tokens.

4. Uso de la interfaz web (panel de administración)

Todo se maneja desde http://localhost:8000 (Vue Router, sin recargar página).

Modo oscuro: el ícono de sol/luna en la barra de navegación alterna entre claro y oscuro. La preferencia se guarda en localStorage (por navegador, no por cuenta) y, si nunca la tocaste, arranca siguiendo el modo del sistema operativo. El cambio se aplica antes del primer render — no hay parpadeo del tema equivocado al cargar la página.

Login / Registro

/login y /register. El registro crea la cuenta con rol global user automáticamente, pero no loguea al usuario: queda pendiente de aprobación. La pantalla muestra un mensaje de "Cuenta creada" y el login devuelve error hasta que un admin la aprueba desde el panel de usuarios (sección 4). Excepción: si la base de usuarios está vacía, esa primera cuenta se crea como admin y queda logueada de una (ver sección 3).

Dashboard

/ — accesos rápidos a Clientes OAuth, Personal Access Tokens, Apps autorizadas y (si sos admin) Usuarios.

Clientes OAuth

/clients — cada usuario ve y gestiona sus propios clientes (apps que registró). Al crear uno se elige:

El client_secret se muestra una sola vez, justo después de crear el cliente. Copiarlo en ese momento.

Personal Access Tokens

/tokens — tokens tipo "API key" para el propio usuario, sin pasar por el flujo de consentimiento. Se elige nombre y scopes. El token completo también se muestra una única vez.

Apps autorizadas

/authorized-apps — lista de apps de terceros a las que el usuario le dio acceso a su cuenta vía /oauth/authorize, con opción de revocar el acceso en cualquier momento.

Usuarios (solo admins)

/admin/users — alta, edición (nombre, email, contraseña, rol global, activo/desactivado) y baja de cualquier usuario del sistema. Protecciones incluidas: no podés borrarte a vos mismo, no podés quitarte el rol admin a vos mismo, no se puede borrar al último administrador. Un usuario desactivado no puede iniciar sesión.

Borrado = papelera (soft delete). "Borrar" un usuario no lo elimina de la base: lo oculta (desaparece de logins, listados y de cualquier chequeo de permisos) pero queda recuperable. El botón "Ver papelera" arriba a la derecha del listado muestra los usuarios borrados, con dos acciones por fila: Restaurar (vuelve a aparecer tal cual estaba) o Borrar definitivamente (ahí sí, sin vuelta atrás).
Registros pendientes de aprobación. Arriba a la derecha del listado hay tres pestañas: Ver activos, Pendientes (con un contador si hay alguno) y Ver papelera. Todo el que se registra por /register aparece en "Pendientes" hasta que un admin lo aprueba (ahí ya puede iniciar sesión) o lo rechaza (va a la papelera, igual que un borrado normal — se puede restaurar). Un usuario dado de alta directamente por un admin (el formulario "Nuevo usuario" de esta misma pantalla) queda aprobado automáticamente.

Analíticas (solo admins)

/admin/analytics — ver sección 5.

5. Analíticas: sesiones, logins y actividad

Pantalla de monitoreo para el admin: cada vez que Passport emite un access token (cualquier grant type) o alguien inicia sesión en el panel, queda un registro en login_events. Con eso se arman:

El seeder de demo genera 30 días de actividad sintética para que estos gráficos no arranquen vacíos. En un despliegue real, los datos son 100% eventos reales — no hay nada que "activar".

Misma info disponible por API: GET /api/analytics/summary?days=30 (scope users:read, requiere el permiso users.view — igual que el resto del panel admin).

6. Roles, permisos y roles por app

Roles globales

Gobiernan el panel de administración de esta app (spatie/laravel-permission):

RolPermisos
adminusers.view, users.create, users.update, users.delete
userNinguno extra — gestiona su propio perfil, sus clientes OAuth y sus tokens.

Roles por app

Independientes del rol global: un admin le puede asignar a cualquier usuario un rol (admin o user) específico de una app registrada, desde el panel de edición de usuario ("Roles por app"). Sirve para que, por ejemplo, un usuario sea admin dentro de la app Angular pero solo user dentro de la app NestJS — sin tocar su rol global.

Esa información viaja en la respuesta de la API (app_roles, ver sección 8): cada app ve únicamente el rol que ese usuario tiene en ella, resuelto automáticamente según el client_id del token usado en la request.

7. Idioma (español / inglés / catalán)

El selector está arriba a la derecha en la barra de navegación (ES / EN / CA). Cambiarlo hace un POST a /web-api/locale, guarda el idioma en la sesión y recarga la página — el servidor decide el idioma con el que renderiza esa carga (mensajes de validación/autenticación vía laravel-lang, más los textos propios de la app en lang/{es,en,ca}/messages.php).

IdiomaMensajes del servidorInterfaz (SPA Vue)
ES (español, por defecto)✓✓
EN (inglés)✓✓ — traducción completa (nav, botones, formularios, tablas, gráficos)
CA (catalán)✓✓ — traducción completa

La interfaz usa vue-i18n con diccionarios en resources/js/i18n/{es,en,ca}.js (168 claves cada uno). El locale de arranque del frontend se inyecta desde el servidor (window.__LOCALE__ en app.blade.php), así que la SPA nace ya en el idioma correcto sin parpadeo.

8. Cómo conectar tu app (Angular, NestJS, Flutter, etc.)

Paso 1 — Registrar la app

Desde /clients, crear un cliente OAuth eligiendo el grant type según el tipo de app:

Tipo de appGrant recomendado
SPA (Angular, React) servida al navegadorAuthorization Code + PKCE, cliente público
Backend con secret propio (NestJS, Express)Authorization Code, cliente confidencial, o Client Credentials si no hay usuario final
Servicio interno máquina-a-máquinaClient Credentials

Paso 2 — Login del usuario (Authorization Code + PKCE, ej. Angular)

Redirigir el navegador a:

GET /oauth/authorize
    ?client_id={client_id}
    &redirect_uri={tu_callback}
    &response_type=code
    &scope=users:read users:write
    &state={valor_random}
    &code_challenge={challenge_pkce}
    &code_challenge_method=S256

El usuario loguea (o ya tiene sesión) y aprueba el acceso. Passport redirige a redirect_uri con ?code=...&state=....

Paso 3 — Canjear el código por un token

curl -X POST http://localhost:8000/oauth/token \
  -d grant_type=authorization_code \
  -d client_id={client_id} \
  -d code={code} \
  -d redirect_uri={tu_callback} \
  -d code_verifier={verifier_pkce}

Alternativa — Client Credentials (ej. NestJS backend-a-backend)

curl -X POST http://localhost:8000/oauth/token \
  -d grant_type=client_credentials \
  -d client_id={client_id} \
  -d client_secret={client_secret} \
  -d scope="users:read"
Un token de client_credentials no tiene usuario asociado: actúa como "servicio de confianza" y puede listar/crear/editar usuarios sin más chequeo que el scope del token.

9. API REST completa: construí tu propio panel admin

Todo lo que hace la SPA Vue por sesión también está disponible por token OAuth2. Esto significa que un cliente Angular, Flutter, o lo que sea, puede ser un admin completo — no solo consultar usuarios — sin depender de la sesión de Laravel. Todos los endpoints requieren Authorization: Bearer {access_token}.

Usuarios y roles (scopes users:read / users:write)

MétodoRutaDescripción
GET/api/userWhoami: usuario del token actual, con roles y app_roles.
GET/api/usersListar usuarios (requiere users.view, salvo client_credentials).
POST/api/usersCrear usuario (requiere users.create).
GET/api/users/{id}Ver un usuario. Siempre permitido sobre uno mismo.
PUT/api/users/{id}Editar. Uno mismo puede cambiar nombre/email/contraseña; rol/estado y otros usuarios requieren users.update.
DELETE/api/users/{id}Borrar — soft delete, va a la papelera (requiere users.delete; no se puede autoeliminar).
GET/api/users/trashedListar usuarios en la papelera (requiere users.view).
POST/api/users/{id}/restoreRestaurar un usuario borrado (requiere users.update).
DELETE/api/users/{id}/forceBorrado definitivo, sin vuelta atrás (requiere users.delete; el usuario tiene que estar en la papelera).
GET/api/users/pendingListar registros pendientes de aprobación (requiere users.view).
POST/api/users/{id}/approveAprobar un registro pendiente (requiere users.update).
GET/api/users/{id}/app-rolesRoles del usuario por app (propio siempre permitido; otros requieren users.view).
POST/api/users/{id}/app-rolesAsignar un rol en una app (requiere users.update, sin excepción para uno mismo).
DELETE/api/users/{id}/app-roles/{clientId}/{role}Quitar un rol en una app (requiere users.update).
GET/api/appsListar todas las apps registradas (requiere users.view) — para armar el selector de roles-por-app.
GET/api/analytics/summary?days=30KPIs, logins por día/app, usuarios más activos y actividad reciente (requiere users.view).

Ejemplo de respuesta de usuario

{
  "id": 2,
  "name": "Carla Gómez",
  "email": "carla@example.com",
  "is_active": true,
  "roles": ["user"],
  "app_roles": ["admin"],
  "created_at": "2026-07-25T08:52:39.000000Z"
}

roles es el rol global; app_roles es el rol de ese usuario específicamente en la app dueña del token usado para la request (útil para que cada app decida qué mostrar/permitir sin tener su propio sistema de permisos). El campo deleted_at solo aparece (no-null) en las respuestas de /api/users/trashed.

Cuenta propia: clientes, tokens y apps autorizadas (scopes account:read / account:write)

Equivalente por token a /web-api/clients, /personal-access-tokens y /authorized-apps de la sección 4 — siempre sobre el usuario dueño del token (no aplica a client_credentials, que no tiene usuario asociado):

MétodoRutaDescripción
GET/api/clientsListar tus clientes OAuth (apps que registraste).
POST/api/clientsRegistrar un cliente nuevo (devuelve plain_secret una sola vez).
PUT/api/clients/{id}Editar nombre/redirect URIs.
DELETE/api/clients/{id}Revocar el cliente y sus tokens.
GET/api/personal-access-tokensListar tus Personal Access Tokens.
POST/api/personal-access-tokensCrear uno (el token se muestra una sola vez).
DELETE/api/personal-access-tokens/{id}Revocarlo.
GET/api/authorized-appsApps de terceros a las que le diste acceso.
DELETE/api/authorized-apps/{tokenId}Revocarle el acceso a una.

Scopes disponibles

ScopePermite
users:readLeer usuarios y roles por app
users:writeCrear, editar y borrar usuarios; asignar roles por app
account:readLeer tus clientes OAuth, tokens y apps autorizadas
account:writeGestionar tus clientes OAuth, tokens y apps autorizadas

No hay scope por defecto: cada app debe pedir explícitamente los que necesita al iniciar el flujo OAuth (ej. scope=users:read users:write account:read account:write para un admin completo).

Client credentials y recursos self-service: un token client_credentials no tiene usuario asociado, así que /api/clients, /api/personal-access-tokens y /api/authorized-apps le devuelven 422 (no hay "cuenta propia" que gestionar). Sí puede usar /api/users y /api/apps como servicio de confianza.

10. Documentación interactiva (Swagger)

Disponible en http://localhost:8000/api/documentation. Incluye el botón Authorize para probar los tres flujos (Authorization Code, Client Credentials, Password) directamente desde el navegador, usando el cliente de ejemplo "Swagger UI" sembrado (sección 3).

El JSON crudo de la especificación OpenAPI está en /docs.

11. Referencia rápida de rutas

Interfaz web (sesión, uso interno de esta app)

RutaQué hace
POST /web-api/registerRegistro — crea la cuenta pendiente, no loguea.
POST /web-api/login / /logoutAutenticación por sesión.
GET /web-api/meUsuario logueado (con roles/permisos).
GET/POST/PUT/DELETE /web-api/clientsClientes OAuth propios.
GET/POST/DELETE /web-api/personal-access-tokensPersonal Access Tokens propios.
GET/DELETE /web-api/authorized-appsApps de terceros autorizadas.
GET/POST/PUT/DELETE /web-api/admin/usersAdministración de usuarios (admin).
GET /web-api/admin/users/trashedUsuarios en la papelera (admin).
POST /web-api/admin/users/{id}/restoreRestaurar un usuario borrado (admin).
DELETE /web-api/admin/users/{id}/forceBorrado definitivo (admin).
GET /web-api/admin/users/pendingRegistros pendientes de aprobación (admin).
POST /web-api/admin/users/{id}/approveAprobar un registro pendiente (admin).
GET /web-api/admin/appsListado de todas las apps registradas (admin).
GET/POST/DELETE /web-api/admin/users/{id}/app-rolesRoles por app de un usuario (admin).
GET /web-api/admin/analytics/summarySesiones, logins y actividad (admin).
POST /web-api/localeCambiar idioma (es/en/ca).

OAuth2 (Passport)

RutaQué hace
GET /oauth/authorizeInicio del flujo Authorization Code (pantalla de consentimiento).
POST /oauth/tokenEmisión/renovación de tokens (todos los grant types).

API REST (Bearer token, para apps externas — Angular, Flutter, lo que sea)

RutaQué hace
GET /api/userWhoami.
GET/POST/PUT/DELETE /api/usersGestión de usuarios (DELETE = papelera).
GET /api/users/trashedUsuarios en la papelera.
POST /api/users/{id}/restoreRestaurar un usuario borrado.
DELETE /api/users/{id}/forceBorrado definitivo.
GET /api/users/pendingRegistros pendientes de aprobación.
POST /api/users/{id}/approveAprobar un registro pendiente.
GET/POST/DELETE /api/users/{id}/app-rolesRoles por app de un usuario.
GET /api/appsListado de apps registradas.
GET /api/analytics/summarySesiones, logins y actividad.
GET/POST/PUT/DELETE /api/clientsTus clientes OAuth.
GET/POST/DELETE /api/personal-access-tokensTus Personal Access Tokens.
GET/DELETE /api/authorized-appsApps de terceros que autorizaste.