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.).
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:
resources/scss/), sin frameworks de utility-classes./api/*) — el mismo conjunto de funciones del
panel Vue (usuarios, roles por app, clientes OAuth, personal access tokens, apps autorizadas), pero
autenticada 100% por token OAuth2 (sin sesión ni cookies). Con esto se puede construir otro admin
entero en Angular, Flutter, o cualquier otro cliente — no hace falta usar la SPA Vue..env)composer install
npm install
php artisan migrate --seed
npm run build
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.
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.
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.
MAIL_MAILER=log — los emails (hoy, solo el de
recuperación de contraseña, ver sección 4) se escriben en storage/logs/laravel.log en vez de
enviarse de verdad, para no necesitar SMTP en desarrollo. Para producción, configurá un mailer real en
.env (MAIL_MAILER, MAIL_HOST, etc. — cualquiera soportado por
Laravel). El envío va encolado (ShouldQueue), así que también necesitás un worker corriendo
(php artisan queue:work, o composer run dev en desarrollo).
El seeder (database/seeders/DatabaseSeeder.php) crea una cuenta admin y algunos usuarios de
ejemplo para poder probar la app de inmediato:
admin@example.compasswordcarla@example.compasswordmartin@example.com y bea@example.com, solo están para que la sección de analíticas tenga datos de entrada.nuevo@example.com / password sin aprobar, para que la
pestaña "Pendientes" del panel de usuarios (sección 4) no arranque vacía.
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).
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.
| Nombre | Grant type | Uso |
|---|---|---|
| Demo SPA (Authorization Code + PKCE) | authorization_code | Cliente público (sin secret), para apps tipo SPA/móvil. |
| Demo Service (Client Credentials) | client_credentials | Comunicación máquina-a-máquina, sin usuario. |
| Demo First-Party App (Password Grant) | password | Apps propias de confianza, login directo usuario/contraseña. |
| Swagger UI | authorization_code, client_credentials | Para probar el botón "Authorize" de la documentación interactiva. |
| Personal Access Client | personal_access | Interno — necesario para poder emitir Personal Access Tokens. |
Todo se maneja desde http://localhost:8000 (Vue Router, sin recargar página).
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 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).
POST /web-api/login admite 5 intentos por minuto por
combinación de email + IP (frena fuerza bruta de contraseña sin afectar a otros usuarios que compartan
IP), y POST /web-api/register 5 por minuto por IP. Al superar el límite, la respuesta es
429 Too Many Requests con un header Retry-After.
Desde /login → "¿Olvidaste tu contraseña?" (/forgot-password), cualquiera puede pedir
un enlace de restablecimiento con solo su email. La respuesta es siempre la misma, exista o
no una cuenta con ese email — lo contrario permitiría enumerar qué emails están registrados probando uno por
uno. Si la cuenta existe, se manda un email (ver la nota sobre MAIL_MAILER en la sección 2) con
un enlace a /reset-password?token=...&email=..., válido durante 60 minutos y
de un solo uso.
user.password_reset en el audit log (sección 7). El motivo más común para resetear una
contraseña es sospechar que se filtró, así que no tiene sentido dejar vigente lo que se emitió con la
vieja.
/ — accesos rápidos a Clientes OAuth, Personal Access Tokens, Apps autorizadas y (si sos admin)
Usuarios.
/clients — cada usuario ve y gestiona sus propios clientes (apps que registró). Al
crear uno se elige:
client_secret se muestra una sola vez, justo después de
crear el cliente. Copiarlo en ese momento.Cada cliente tiene, además, un botón "Webhooks" para registrar endpoints que reciben avisos cuando pasa algo relevante para esa app (ver sección 12) — sin tener que hacer polling.
client_secret) tienen un botón
"Rotar secret". Genera un secret nuevo y lo muestra una sola vez, igual que al crear el
cliente — pero el secret anterior sigue siendo válido durante 48 horas (grace period), así
que podés actualizar la config de tu app sin downtime: mientras dure el grace period, ambos secrets
(viejo y nuevo) funcionan contra POST /oauth/token. Pasadas las 48 horas, o si rotás de
nuevo antes, el secret anterior deja de servir. No aplica a clientes públicos (Authorization Code + PKCE sin
secret) — no hay nada que rotar.
/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.
/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.
/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.
/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.
/security — cualquier usuario (no solo admins) puede activar 2FA acá. Ver sección 5.
/admin/analytics — ver sección 6.
/admin/audit-log — ver sección 7.
Cualquier usuario (no hace falta ser admin) puede activar 2FA con TOTP desde /security —
compatible con Google Authenticator, Authy, 1Password, o cualquier app que hable el estándar TOTP
(RFC 6238). Es una capa extra sobre la contraseña: una vez activo, el login pide además un código de 6
dígitos.
El login pasa a ser de dos pasos: primero email + contraseña (esto no crea sesión todavía); si el usuario tiene 2FA activo, el servidor responde pidiendo el código en vez de loguear directo. Recién con un código válido (TOTP o de recuperación) se crea la sesión. Un código de recuperación usado se invalida al instante — no sirve una segunda vez.
/web-api/login/2fa), confirmar la
activación (/2fa/confirm), desactivar (/2fa/disable) y regenerar códigos de
recuperación (/2fa/recovery-codes) admiten 6 intentos por minuto — el espacio de un código
TOTP de 6 dígitos es chico como para no limitarlo.
/security, piden confirmar la contraseña actual. Desactivar borra el secreto y los códigos de
recuperación por completo; para volver a activarlo hay que escanear un QR nuevo (no se reutiliza el
secreto anterior). Regenerar invalida los 8 códigos viejos y muestra 8 nuevos.
POST /oauth/token con
grant_type=password exige además un campo otp con un código TOTP o de
recuperación válido, en el mismo request — sin él, o con uno inválido, el servidor responde
400 invalid_grant igual que con una contraseña incorrecta (no delata si el problema fue la
contraseña o el 2FA). Los usuarios sin 2FA no notan ningún cambio.
curl -X POST /oauth/token -d grant_type=password \
-d client_id=... -d client_secret=... \
-d username=ana@example.com -d password=... \
-d otp=123456 -d scope="users:read"
Cada activación, desactivación y regeneración de códigos queda registrada en el audit log (sección 7).
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:
/oauth/authorize), cuántas se aprobaron y cuántas se denegaron, con la tasa de
aprobación resultante.users:read, account:write, etc.)
aparecen más entre los tokens emitidos en el período.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).
Pantalla de auditoría para el admin (/admin/audit-log): registra automáticamente los cambios que
importan desde el punto de vista de seguridad/gobernanza — no hace falta activar nada, queda un renglón cada vez
que ocurre alguno de estos eventos:
| Acción | Cuándo se registra | Qué guarda en "Detalles" |
|---|---|---|
user.created | Un admin da de alta un usuario desde el panel. | Nombre, email y rol asignado. |
user.updated | Un admin edita nombre, email, rol, estado activo/inactivo o contraseña de un usuario. | Solo los campos que efectivamente cambiaron (antes → después). La contraseña nunca se guarda en texto plano, solo si cambió o no. |
user.deleted | Un admin borra (soft delete) un usuario. | — |
user.restored | Un admin restaura un usuario desde la papelera. | — |
user.force_deleted | Un admin borra definitivamente un usuario de la papelera. | — |
user.approved | Un admin aprueba un registro pendiente. | — |
user.app_role_assigned | Un admin asigna un rol por-app a un usuario. | Client y rol asignado. |
user.app_role_revoked | Un admin quita un rol por-app a un usuario. | Client y rol quitado. |
client.created | Se registra un cliente OAuth nuevo (panel o API). | Grant type y si es confidencial. |
client.revoked | Se revoca un cliente OAuth (panel o API) — cascada a sus tokens. | Cuántos tokens quedaron revocados. |
user.2fa_enabled | Un usuario activa 2FA. | — |
user.2fa_disabled | Un usuario desactiva 2FA. | — |
user.2fa_recovery_regenerated | Un usuario regenera sus códigos de recuperación. | — |
user.session_revoked | Un admin revoca un token OAuth ("sesión") de un usuario. | — |
user.sessions_revoked_all | Un admin cierra todas las sesiones activas de un usuario de una vez. | Cuántos tokens quedaron revocados. |
client.webhook_created | Se registra un webhook nuevo en un cliente OAuth. | URL y eventos suscritos. |
client.webhook_deleted | Se borra un webhook. | URL. |
client.secret_rotated | El dueño de un cliente confidencial rota su client_secret. | Hasta cuándo sigue valiendo el secret anterior. |
user.password_reset | Un usuario restablece su contraseña vía "¿Olvidaste tu contraseña?". | Cuántas sesiones quedaron revocadas. |
Cada evento guarda además quién lo hizo (o "(sistema)" si no hay actor), IP, y cuándo. Es filtrable por sujeto (Usuarios / Clientes OAuth) y por acción, y está paginado.
name/redirect_uris en un cliente
OAuth existente (solo se audita creación y revocación), y acciones de auto-servicio de un usuario sobre sus
propios clientes/tokens/apps autorizadas (ese flujo no pasa por rutas de admin).
Requiere el permiso audit.view (nuevo — separado de users.* porque también cubre
clientes OAuth, no solo usuarios). Se lo asigna automáticamente al rol admin. Misma info por API:
GET /api/audit-log (scope users:read).
Gobiernan el panel de administración de esta app (spatie/laravel-permission):
| Rol | Permisos |
|---|---|
| admin | users.view, users.create, users.update, users.delete, audit.view |
| user | Ninguno extra — gestiona su propio perfil, sus clientes OAuth y sus tokens. |
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 11): 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.
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).
| Idioma | Mensajes del servidor | Interfaz (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.
Desde /clients, crear un cliente OAuth eligiendo el grant type según el tipo de app:
| Tipo de app | Grant recomendado |
|---|---|
| SPA (Angular, React) servida al navegador | Authorization 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áquina | Client Credentials |
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=....
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}
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"
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.
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}.
users:read / users:write)| Método | Ruta | Descripción |
|---|---|---|
| GET | /api/user | Whoami: usuario del token actual, con roles y app_roles. |
| GET | /api/users | Listar usuarios (requiere users.view, salvo client_credentials). |
| POST | /api/users | Crear 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/trashed | Listar usuarios en la papelera (requiere users.view). |
| POST | /api/users/{id}/restore | Restaurar un usuario borrado (requiere users.update). |
| DELETE | /api/users/{id}/force | Borrado definitivo, sin vuelta atrás (requiere users.delete; el usuario tiene que estar en la papelera). |
| GET | /api/users/pending | Listar registros pendientes de aprobación (requiere users.view). |
| POST | /api/users/{id}/approve | Aprobar un registro pendiente (requiere users.update). |
| GET | /api/users/{id}/app-roles | Roles del usuario por app (propio siempre permitido; otros requieren users.view). |
| POST | /api/users/{id}/app-roles | Asignar 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/users/{id}/sessions | Tokens OAuth activos de ese usuario ("sesiones") (requiere users.view). |
| DELETE | /api/users/{id}/sessions/{tokenId} | Revocar una sesión puntual (requiere users.update). |
| DELETE | /api/users/{id}/sessions | Revocar todas las sesiones activas de ese usuario (requiere users.update). |
| GET | /api/apps | Listar todas las apps registradas (requiere users.view) — para armar el selector de roles-por-app. |
| GET | /api/analytics/summary?days=30 | KPIs, logins por día/app, usuarios más activos, funnel de autorización OAuth, tokens activos por cliente, uso de scopes y actividad reciente (requiere users.view). |
| GET | /api/audit-log?action=&subject_type=&per_page=25 | Historial de auditoría paginado: altas/bajas de usuarios, cambios de rol, clientes OAuth creados/revocados (requiere audit.view). |
{
"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.
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étodo | Ruta | Descripción |
|---|---|---|
| GET | /api/clients | Listar tus clientes OAuth (apps que registraste). |
| POST | /api/clients | Registrar 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. |
| POST | /api/clients/{id}/rotate-secret | Rotar el client_secret (solo clientes confidenciales). Devuelve plain_secret una sola vez; el secret anterior sigue valiendo 48h. |
| GET | /api/personal-access-tokens | Listar tus Personal Access Tokens. |
| POST | /api/personal-access-tokens | Crear uno (el token se muestra una sola vez). |
| DELETE | /api/personal-access-tokens/{id} | Revocarlo. |
| GET | /api/authorized-apps | Apps de terceros a las que le diste acceso. |
| DELETE | /api/authorized-apps/{tokenId} | Revocarle el acceso a una. |
| GET | /api/clients/{id}/webhooks | Listar los webhooks de uno de tus clientes (ver sección 12). |
| POST | /api/clients/{id}/webhooks | Registrar un webhook nuevo (devuelve secret una sola vez). |
| PUT | /api/clients/{id}/webhooks/{webhookId} | Editar URL, eventos o pausar/reanudar. |
| DELETE | /api/clients/{id}/webhooks/{webhookId} | Borrar un webhook. |
| GET | /api/clients/{id}/webhooks/{webhookId}/deliveries | Historial paginado de intentos de entrega. |
| POST | /api/clients/{id}/webhooks/{webhookId}/deliveries/{deliveryId}/retry | Reintentar manualmente una entrega puntual. |
| Scope | Permite |
|---|---|
users:read | Leer usuarios y roles por app |
users:write | Crear, editar y borrar usuarios; asignar roles por app |
account:read | Leer tus clientes OAuth, tokens y apps autorizadas |
account:write | Gestionar 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 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.
Cada cliente OAuth puede registrar uno o más endpoints webhook desde /clients → botón
"Webhooks" (o por API, sección 11). Cuando pasa algo relevante para ese cliente, este servidor le manda un
POST con el evento — no hace falta consultar la API cada tanto para enterarse.
| Evento | Cuándo se dispara | A quién le llega |
|---|---|---|
token.revoked | Se revoca un token de ese cliente — por el usuario dueño (self-service), por un admin (Sesiones activas, sección 4), o en cascada al revocar el cliente entero. | Los webhooks de ese cliente. |
user.deactivated | Un admin desactiva un usuario. | Los webhooks de todo cliente que tenga al menos un token vigente para ese usuario en ese momento. |
Con token.revoked tu app se entera al instante de que ya no tiene acceso a la cuenta de ese
usuario (por ejemplo, para cerrarle la sesión local), en vez de esperar a que el token expire solo.
localhost, IPs loopback (127.0.0.0/8), privadas
(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) y de link-local
(169.254.0.0/16 — incluye el endpoint de metadata de instancia en AWS/GCP/Azure,
169.254.169.254). También esquemas que no sean http/https.
Sin esto, cualquiera que registre un client podría hacer que este servidor le pegue a servicios internos
(Redis, la propia API, metadata de la nube) y leer la respuesta en el panel — el clásico ataque SSRF.
La validación se repite justo antes de cada envío, no solo al crear el webhook: un dominio público hoy
podría resolver a una IP interna mañana (DNS rebinding).
Cada entrega incluye el header X-Webhook-Signature: sha256={firma}, un HMAC-SHA256 del body crudo
(JSON) usando el secret que se te mostró al crear el webhook (una sola vez, igual que un
client_secret — si lo perdiste, borrá el webhook y creá uno nuevo). Verificalo antes de confiar en
el payload:
// Node.js
import crypto from 'crypto';
function isValid(rawBody: string, signatureHeader: string, secret: string): boolean {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
El body tiene forma {"event": "token.revoked", "data": {...}}. El header
X-Webhook-Event repite el nombre del evento, por si preferís ramificar por header sin parsear el body.
2xx (o no responde en 5 segundos), se reintenta hasta 2 veces más
con backoff creciente (10s, 60s, 300s). Después de 3 intentos fallidos, se abandona esa entrega — no hay
cola de reintento infinita. Cada intento (éxito o fracaso) queda registrado; el panel muestra el estado del
último envío junto a cada webhook (código de respuesta o "pendiente").
El botón "Ver historial" de cada webhook (junto a Pausar/Borrar) abre el listado paginado de
todas las entregas — no solo la última. Cada fila muestra el código de respuesta (o "pendiente" si nunca
respondió), el evento, cuándo se creó y cuántos intentos acumula. Las entregas que no llegaron a un
2xx tienen un botón "Reintentar": vuelve a encolar el mismo registro (no crea
uno nuevo — el contador de intentos sigue acumulando sobre esa entrega) para forzar un envío inmediato sin
esperar al backoff automático. Útil después de arreglar algo en tu endpoint y no querer esperar los 5 minutos
del backoff.
Cada alta y baja de un webhook queda en el audit log (sección 7): client.webhook_created /
client.webhook_deleted.
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.
| Ruta | Qué hace |
|---|---|
POST /web-api/register | Registro — crea la cuenta pendiente, no loguea. |
POST /web-api/login / /logout | Autenticación por sesión. Si el usuario tiene 2FA, /login devuelve {"two_factor":true} en vez de loguear. |
POST /web-api/login/2fa | Segundo paso del login con 2FA activo (código TOTP o de recuperación). |
POST /web-api/forgot-password | Pedir el enlace de restablecimiento (respuesta genérica, exista o no la cuenta). |
POST /web-api/reset-password | Confirmar el restablecimiento con el token del email — revoca todas las sesiones activas. |
GET /web-api/me | Usuario logueado (con roles/permisos y two_factor_enabled). |
POST /web-api/2fa/setup | Genera secreto pendiente + QR para activar 2FA. |
POST /web-api/2fa/confirm | Confirma la activación con un código real — devuelve los códigos de recuperación una vez. |
POST /web-api/2fa/disable | Desactiva 2FA (requiere contraseña actual). |
POST /web-api/2fa/recovery-codes | Regenera los códigos de recuperación (requiere contraseña actual). |
GET/POST/PUT/DELETE /web-api/clients | Clientes OAuth propios. |
POST /web-api/clients/{id}/rotate-secret | Rotar el client_secret de un cliente confidencial propio (secret anterior válido 48h más). |
GET/POST/DELETE /web-api/personal-access-tokens | Personal Access Tokens propios. |
GET/DELETE /web-api/authorized-apps | Apps de terceros autorizadas. |
GET/POST/PUT/DELETE /web-api/clients/{id}/webhooks | Webhooks de un cliente propio. |
GET /web-api/clients/{id}/webhooks/{webhookId}/deliveries | Historial paginado de entregas de un webhook propio. |
POST /web-api/clients/{id}/webhooks/{webhookId}/deliveries/{deliveryId}/retry | Reintentar manualmente una entrega. |
GET/POST/PUT/DELETE /web-api/admin/users | Administración de usuarios (admin). |
GET /web-api/admin/users/trashed | Usuarios en la papelera (admin). |
POST /web-api/admin/users/{id}/restore | Restaurar un usuario borrado (admin). |
DELETE /web-api/admin/users/{id}/force | Borrado definitivo (admin). |
GET /web-api/admin/users/pending | Registros pendientes de aprobación (admin). |
POST /web-api/admin/users/{id}/approve | Aprobar un registro pendiente (admin). |
GET /web-api/admin/apps | Listado de todas las apps registradas (admin). |
GET/POST/DELETE /web-api/admin/users/{id}/app-roles | Roles por app de un usuario (admin). |
GET /web-api/admin/users/{id}/sessions | Tokens OAuth activos de un usuario (admin). |
DELETE /web-api/admin/users/{id}/sessions/{tokenId} | Revocar una sesión puntual (admin). |
DELETE /web-api/admin/users/{id}/sessions | Revocar todas las sesiones activas de un usuario (admin). |
GET /web-api/admin/analytics/summary | Sesiones, logins, funnel de autorización, tokens activos, uso de scopes y actividad (admin). |
GET /web-api/admin/audit-log | Historial de auditoría paginado y filtrable (admin). |
POST /web-api/oauth/authorize-events | Registro interno del funnel de autorización (llamado por la pantalla de consentimiento, no pensado para uso directo). |
POST /web-api/locale | Cambiar idioma (es/en/ca). |
| Ruta | Qué hace |
|---|---|
GET /oauth/authorize | Inicio del flujo Authorization Code (pantalla de consentimiento). |
POST /oauth/token | Emisión/renovación de tokens (todos los grant types). |
/oauth/* admiten 20 requests por minuto por
combinación de IP + client_id — protege especialmente al Password Grant (usuario/contraseña
directos contra /oauth/token) de fuerza bruta, sin penalizar a otros clientes si uno solo
está siendo atacado. Va además del throttle genérico (60/min por IP) que Passport ya trae
por defecto en /oauth/token.
| Ruta | Qué hace |
|---|---|
GET /api/user | Whoami. |
GET/POST/PUT/DELETE /api/users | Gestión de usuarios (DELETE = papelera). |
GET /api/users/trashed | Usuarios en la papelera. |
POST /api/users/{id}/restore | Restaurar un usuario borrado. |
DELETE /api/users/{id}/force | Borrado definitivo. |
GET /api/users/pending | Registros pendientes de aprobación. |
POST /api/users/{id}/approve | Aprobar un registro pendiente. |
GET/POST/DELETE /api/users/{id}/app-roles | Roles por app de un usuario. |
GET/DELETE /api/users/{id}/sessions | Tokens OAuth activos de un usuario; revocar uno o todos. |
GET /api/apps | Listado de apps registradas. |
GET /api/analytics/summary | Sesiones, logins, funnel de autorización, tokens activos, uso de scopes y actividad. |
GET /api/audit-log | Historial de auditoría paginado y filtrable. |
GET/POST/PUT/DELETE /api/clients | Tus clientes OAuth. |
POST /api/clients/{id}/rotate-secret | Rotar el client_secret de un cliente confidencial (secret anterior válido 48h más). |
GET/POST/DELETE /api/personal-access-tokens | Tus Personal Access Tokens. |
GET/DELETE /api/authorized-apps | Apps de terceros que autorizaste. |
GET/POST/PUT/DELETE /api/clients/{id}/webhooks | Webhooks de uno de tus clientes. |
GET /api/clients/{id}/webhooks/{webhookId}/deliveries | Historial paginado de entregas de un webhook. |
POST /api/clients/{id}/webhooks/{webhookId}/deliveries/{deliveryId}/retry | Reintentar manualmente una entrega. |