Laravel-based OAuth/IAM service with Passport, Vue 3 frontend, and Docker Compose setup for local and production deployment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
621 lines
33 KiB
HTML
621 lines
33 KiB
HTML
<!doctype html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>OAuth2 Server — Manual de uso</title>
|
|
<style>
|
|
@page {
|
|
margin: 22mm 18mm;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
|
|
color: #1f2430;
|
|
font-size: 12.5px;
|
|
line-height: 1.55;
|
|
}
|
|
.cover {
|
|
height: 245mm;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
page-break-after: always;
|
|
}
|
|
.cover .badge {
|
|
display: inline-block;
|
|
background: #4f46e5;
|
|
color: white;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: .08em;
|
|
text-transform: uppercase;
|
|
padding: 4px 12px;
|
|
border-radius: 999px;
|
|
margin-bottom: 18px;
|
|
width: fit-content;
|
|
}
|
|
.cover h1 {
|
|
font-size: 34px;
|
|
margin: 0 0 10px;
|
|
color: #111827;
|
|
}
|
|
.cover p.subtitle {
|
|
font-size: 15px;
|
|
color: #4b5563;
|
|
max-width: 420px;
|
|
margin: 0 0 28px;
|
|
}
|
|
.cover .meta {
|
|
font-size: 11.5px;
|
|
color: #9ca3af;
|
|
border-top: 1px solid #e5e7eb;
|
|
padding-top: 12px;
|
|
width: fit-content;
|
|
}
|
|
h1, h2, h3 { color: #111827; }
|
|
h2 {
|
|
font-size: 19px;
|
|
margin-top: 0;
|
|
padding-bottom: 8px;
|
|
border-bottom: 2px solid #4f46e5;
|
|
}
|
|
h3 {
|
|
font-size: 14.5px;
|
|
margin-bottom: 6px;
|
|
color: #4338ca;
|
|
}
|
|
section {
|
|
page-break-before: always;
|
|
padding-top: 4mm;
|
|
}
|
|
section:first-of-type { page-break-before: avoid; }
|
|
p { margin: 6px 0 10px; }
|
|
ul, ol { margin: 6px 0 12px; padding-left: 20px; }
|
|
li { margin-bottom: 4px; }
|
|
code {
|
|
background: #f1f0fb;
|
|
color: #4338ca;
|
|
padding: 1px 5px;
|
|
border-radius: 4px;
|
|
font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
font-size: 11.5px;
|
|
}
|
|
pre {
|
|
background: #1e1e2e;
|
|
color: #e2e2f5;
|
|
padding: 12px 14px;
|
|
border-radius: 8px;
|
|
overflow-x: auto;
|
|
font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
font-size: 10.8px;
|
|
line-height: 1.5;
|
|
page-break-inside: avoid;
|
|
}
|
|
pre code { background: none; color: inherit; padding: 0; }
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 10px 0 16px;
|
|
font-size: 11.5px;
|
|
page-break-inside: avoid;
|
|
}
|
|
th, td {
|
|
border: 1px solid #e5e7eb;
|
|
padding: 6px 9px;
|
|
text-align: left;
|
|
vertical-align: top;
|
|
}
|
|
th {
|
|
background: #f5f5fb;
|
|
color: #374151;
|
|
font-weight: 600;
|
|
}
|
|
tr:nth-child(even) td { background: #fafafa; }
|
|
.box {
|
|
background: #f5f5fb;
|
|
border-left: 4px solid #4f46e5;
|
|
padding: 10px 14px;
|
|
border-radius: 6px;
|
|
margin: 12px 0;
|
|
font-size: 11.8px;
|
|
}
|
|
.box.warn {
|
|
background: #fffbeb;
|
|
border-left-color: #d97706;
|
|
}
|
|
.box strong { color: #111827; }
|
|
.credentials {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin: 12px 0 18px;
|
|
}
|
|
.cred-card {
|
|
flex: 1;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 8px;
|
|
padding: 12px 14px;
|
|
}
|
|
.cred-card h4 {
|
|
margin: 0 0 8px;
|
|
font-size: 12.5px;
|
|
color: #4338ca;
|
|
}
|
|
.cred-card div { margin-bottom: 3px; font-size: 11.5px; }
|
|
.cred-card .label { color: #6b7280; display: inline-block; width: 62px; }
|
|
.toc { list-style: none; padding-left: 0; }
|
|
.toc li {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
border-bottom: 1px dotted #d1d5db;
|
|
padding: 5px 0;
|
|
font-size: 13px;
|
|
}
|
|
.toc .n { color: #4f46e5; font-weight: 600; margin-right: 8px; }
|
|
.pill {
|
|
display: inline-block;
|
|
background: #eef2ff;
|
|
color: #4338ca;
|
|
border-radius: 4px;
|
|
padding: 1px 7px;
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
margin-right: 4px;
|
|
}
|
|
footer.pagefoot {
|
|
margin-top: 22px;
|
|
padding-top: 8px;
|
|
border-top: 1px solid #e5e7eb;
|
|
font-size: 10px;
|
|
color: #9ca3af;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="cover">
|
|
<span class="badge">Documentación técnica</span>
|
|
<h1>OAuth2 Identity Server</h1>
|
|
<p class="subtitle">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.).</p>
|
|
<div class="meta">
|
|
Laravel 12 · Passport 13 · Vue 3 + TypeScript · SCSS/BEM · spatie/laravel-permission · L5-Swagger<br>
|
|
Generado el 25 de julio de 2026
|
|
</div>
|
|
</div>
|
|
|
|
<section>
|
|
<h2>Índice</h2>
|
|
<ul class="toc">
|
|
<li><span><span class="n">1.</span>Qué es esta app</span></li>
|
|
<li><span><span class="n">2.</span>Puesta en marcha</span></li>
|
|
<li><span><span class="n">3.</span>Usuarios por defecto (seed)</span></li>
|
|
<li><span><span class="n">4.</span>Uso de la interfaz web (panel de administración)</span></li>
|
|
<li><span><span class="n">5.</span>Analíticas: sesiones, logins y actividad</span></li>
|
|
<li><span><span class="n">6.</span>Roles, permisos y roles por app</span></li>
|
|
<li><span><span class="n">7.</span>Idioma (español / inglés / catalán)</span></li>
|
|
<li><span><span class="n">8.</span>Cómo conectar tu app (Angular, NestJS, Flutter, etc.)</span></li>
|
|
<li><span><span class="n">9.</span>API REST completa: construí tu propio panel admin</span></li>
|
|
<li><span><span class="n">10.</span>Documentación interactiva (Swagger)</span></li>
|
|
<li><span><span class="n">11.</span>Referencia rápida de rutas</span></li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>1. Qué es esta app</h2>
|
|
<p>
|
|
Es un <strong>servidor de identidad / OAuth2</strong> (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 <strong>usuarios</strong> y sus <strong>roles</strong>.
|
|
</p>
|
|
<p>Provee tres piezas:</p>
|
|
<ul>
|
|
<li><strong>Servidor de autorización OAuth2</strong> — login, pantalla de consentimiento, y emisión de tokens
|
|
con los cuatro flujos estándar: Authorization Code + PKCE, Client Credentials, Password Grant y Personal
|
|
Access Tokens.</li>
|
|
<li><strong>Panel de administración en Vue 3 + TypeScript</strong> — la UI que trae la app "de fábrica",
|
|
servida por el propio Laravel, autenticada por sesión. Estilos en SCSS con nomenclatura BEM
|
|
(<code>resources/scss/</code>), sin frameworks de utility-classes.</li>
|
|
<li><strong>API REST completa</strong> (<code>/api/*</code>) — el <em>mismo</em> 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 <strong>otro admin
|
|
entero</strong> en Angular, Flutter, o cualquier otro cliente — no hace falta usar la SPA Vue.</li>
|
|
</ul>
|
|
<div class="box">
|
|
<strong>Idea central:</strong> cada aplicación externa que quiera loguear usuarios se registra acá como un
|
|
<em>cliente OAuth</em> ("app"). Un mismo usuario puede tener un rol global (admin/user) y, además, un rol
|
|
distinto <em>dentro de cada app</em> (por ejemplo: admin en la app de Angular, usuario normal en la de NestJS).
|
|
</div>
|
|
<div class="box">
|
|
<strong>Dos frentes, misma API subyacente:</strong> 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 <em>mismas</em> capacidades. Elegí el que corresponda: sesión para la UI que ya viene incluida,
|
|
token para construir la tuya propia.
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>2. Puesta en marcha</h2>
|
|
<h3>Requisitos</h3>
|
|
<ul>
|
|
<li>PHP 8.4+, Composer</li>
|
|
<li>Node.js + npm</li>
|
|
<li>SQLite (por defecto, cero configuración — se puede cambiar en <code>.env</code>)</li>
|
|
</ul>
|
|
<h3>Instalación (primera vez)</h3>
|
|
<pre><code>composer install
|
|
npm install
|
|
php artisan migrate --seed
|
|
npm run build</code></pre>
|
|
<div class="box">
|
|
<strong>Frontend:</strong> TypeScript (<code>npm run type-check</code> corre <code>vue-tsc</code> sin
|
|
emitir archivos) y SCSS compilado por Vite (paquete <code>sass</code>) desde
|
|
<code>resources/scss/app.scss</code> — no hay ningún framework de CSS utility-first instalado.
|
|
</div>
|
|
<h3>Levantar todo en desarrollo</h3>
|
|
<pre><code>composer run dev</code></pre>
|
|
<p>Este comando levanta en paralelo: el servidor PHP (<code>php artisan serve</code>), Vite en modo watch, la
|
|
cola de trabajos y los logs. La app queda disponible en <code>http://localhost:8000</code>.</p>
|
|
<div class="box warn">
|
|
<strong>Reiniciar todo de cero:</strong> <code>php artisan migrate:fresh --seed</code> borra la base y
|
|
vuelve a crear los usuarios/clientes de ejemplo descriptos en la sección 3. Los <code>client_secret</code>
|
|
de los clientes OAuth de ejemplo se imprimen en la consola en ese momento — solo se muestran una vez.
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>3. Usuarios por defecto (seed)</h2>
|
|
<p>El seeder (<code>database/seeders/DatabaseSeeder.php</code>) crea una cuenta admin y algunos usuarios de
|
|
ejemplo para poder probar la app de inmediato:</p>
|
|
<div class="credentials">
|
|
<div class="cred-card">
|
|
<h4>🛡 Administrador</h4>
|
|
<div><span class="label">Email:</span> <code>admin@example.com</code></div>
|
|
<div><span class="label">Password:</span> <code>password</code></div>
|
|
<div><span class="label">Rol global:</span> <span class="pill">admin</span></div>
|
|
<div style="margin-top:6px; color:#6b7280;">Acceso al panel de administración de usuarios.</div>
|
|
</div>
|
|
<div class="cred-card">
|
|
<h4>👤 Usuario normal</h4>
|
|
<div><span class="label">Email:</span> <code>carla@example.com</code></div>
|
|
<div><span class="label">Password:</span> <code>password</code></div>
|
|
<div><span class="label">Rol global:</span> <span class="pill">user</span></div>
|
|
<div style="margin-top:6px; color:#6b7280;">Sin acceso al panel admin. Es <strong>admin</strong> dentro de la app "Demo SPA" (rol por-app de ejemplo). Junto con <code>martin@example.com</code> y <code>bea@example.com</code>, solo están para que la sección de analíticas tenga datos de entrada.</div>
|
|
</div>
|
|
</div>
|
|
<div class="box">
|
|
El seeder también crea <code>nuevo@example.com</code> / <code>password</code> sin aprobar, para que la
|
|
pestaña <strong>"Pendientes"</strong> del panel de usuarios (sección 4) no arranque vacía.
|
|
</div>
|
|
<div class="box">
|
|
<strong>Instalación sin seed (producción):</strong> si nunca corriste <code>db:seed</code>, no hay ningún
|
|
usuario en la base. En ese caso, la <strong>primera</strong> cuenta que se registre por <code>/register</code>
|
|
queda automáticamente como <strong>admin</strong>, 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).
|
|
</div>
|
|
|
|
<h3>Clientes OAuth (apps) de ejemplo</h3>
|
|
<p>El seeder también registra apps de prueba, una por cada flujo soportado. El <code>client_id</code> es fijo
|
|
por ejecución (UUID nuevo cada <code>migrate:fresh</code>); el <code>client_secret</code> 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.</p>
|
|
<table>
|
|
<tr><th>Nombre</th><th>Grant type</th><th>Uso</th></tr>
|
|
<tr><td>Demo SPA (Authorization Code + PKCE)</td><td><code>authorization_code</code></td><td>Cliente público (sin secret), para apps tipo SPA/móvil.</td></tr>
|
|
<tr><td>Demo Service (Client Credentials)</td><td><code>client_credentials</code></td><td>Comunicación máquina-a-máquina, sin usuario.</td></tr>
|
|
<tr><td>Demo First-Party App (Password Grant)</td><td><code>password</code></td><td>Apps propias de confianza, login directo usuario/contraseña.</td></tr>
|
|
<tr><td>Swagger UI</td><td><code>authorization_code</code>, <code>client_credentials</code></td><td>Para probar el botón "Authorize" de la documentación interactiva.</td></tr>
|
|
<tr><td>Personal Access Client</td><td><code>personal_access</code></td><td>Interno — necesario para poder emitir Personal Access Tokens.</td></tr>
|
|
</table>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>4. Uso de la interfaz web (panel de administración)</h2>
|
|
<p>Todo se maneja desde <code>http://localhost:8000</code> (Vue Router, sin recargar página).</p>
|
|
<div class="box">
|
|
<strong>Modo oscuro:</strong> el ícono de sol/luna en la barra de navegación alterna entre claro y oscuro.
|
|
La preferencia se guarda en <code>localStorage</code> (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.
|
|
</div>
|
|
|
|
<h3>Login / Registro</h3>
|
|
<p><code>/login</code> y <code>/register</code>. El registro crea la cuenta con rol global <code>user</code>
|
|
automáticamente, pero <strong>no loguea al usuario</strong>: 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). <strong>Excepción:</strong> si la base de usuarios está vacía, esa primera cuenta se
|
|
crea como admin y queda logueada de una (ver sección 3).</p>
|
|
|
|
<h3>Dashboard</h3>
|
|
<p><code>/</code> — accesos rápidos a Clientes OAuth, Personal Access Tokens, Apps autorizadas y (si sos admin)
|
|
Usuarios.</p>
|
|
|
|
<h3>Clientes OAuth</h3>
|
|
<p><code>/clients</code> — cada usuario ve y gestiona <em>sus propios</em> clientes (apps que registró). Al
|
|
crear uno se elige:</p>
|
|
<ul>
|
|
<li><strong>Authorization Code + PKCE</strong>: hay que indicar redirect URIs; opcionalmente "confidencial"
|
|
(con secret) o público (solo PKCE, sin secret).</li>
|
|
<li><strong>Client Credentials</strong>: siempre confidencial, sin redirect URI.</li>
|
|
<li><strong>Password Grant</strong>: para apps propias de confianza.</li>
|
|
</ul>
|
|
<div class="box warn">El <code>client_secret</code> se muestra <strong>una sola vez</strong>, justo después de
|
|
crear el cliente. Copiarlo en ese momento.</div>
|
|
|
|
<h3>Personal Access Tokens</h3>
|
|
<p><code>/tokens</code> — 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.</p>
|
|
|
|
<h3>Apps autorizadas</h3>
|
|
<p><code>/authorized-apps</code> — lista de apps de terceros a las que el usuario le dio acceso a su cuenta vía
|
|
<code>/oauth/authorize</code>, con opción de revocar el acceso en cualquier momento.</p>
|
|
|
|
<h3>Usuarios (solo admins)</h3>
|
|
<p><code>/admin/users</code> — 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.</p>
|
|
<div class="box">
|
|
<strong>Borrado = papelera (soft delete).</strong> "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
|
|
<strong>"Ver papelera"</strong> arriba a la derecha del listado muestra los usuarios borrados, con dos
|
|
acciones por fila: <strong>Restaurar</strong> (vuelve a aparecer tal cual estaba) o <strong>Borrar
|
|
definitivamente</strong> (ahí sí, sin vuelta atrás).
|
|
</div>
|
|
<div class="box">
|
|
<strong>Registros pendientes de aprobación.</strong> Arriba a la derecha del listado hay tres pestañas:
|
|
<em>Ver activos</em>, <strong>Pendientes</strong> (con un contador si hay alguno) y <em>Ver papelera</em>.
|
|
Todo el que se registra por <code>/register</code> aparece en "Pendientes" hasta que un admin lo
|
|
<strong>aprueba</strong> (ahí ya puede iniciar sesión) o lo <strong>rechaza</strong> (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.
|
|
</div>
|
|
|
|
<h3>Analíticas (solo admins)</h3>
|
|
<p><code>/admin/analytics</code> — ver sección 5.</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>5. Analíticas: sesiones, logins y actividad</h2>
|
|
<p>
|
|
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 <code>login_events</code>. Con eso se arman:
|
|
</p>
|
|
<ul>
|
|
<li><strong>KPIs</strong>: usuarios totales, sesiones activas (tokens OAuth vigentes, no revocados ni
|
|
expirados), logins de hoy y de los últimos 7 días.</li>
|
|
<li><strong>Logins por día</strong> (últimos 7/30/90 días, elegible arriba a la derecha): barras apiladas —
|
|
logins de apps (OAuth) vs. logins al panel admin (sesión) — con tooltip al pasar el mouse sobre cada día.</li>
|
|
<li><strong>Logins por app</strong>: qué apps se usan más.</li>
|
|
<li><strong>Usuarios más activos</strong>: ranking por cantidad de logins en el período.</li>
|
|
<li><strong>Actividad reciente</strong>: tabla con los últimos eventos (usuario, app, tipo, IP, cuándo).</li>
|
|
</ul>
|
|
<div class="box">
|
|
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".
|
|
</div>
|
|
<p>Misma info disponible por API: <code>GET /api/analytics/summary?days=30</code> (scope
|
|
<code>users:read</code>, requiere el permiso <code>users.view</code> — igual que el resto del panel admin).</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>6. Roles, permisos y roles por app</h2>
|
|
<h3>Roles globales</h3>
|
|
<p>Gobiernan el panel de administración de esta app (<code>spatie/laravel-permission</code>):</p>
|
|
<table>
|
|
<tr><th>Rol</th><th>Permisos</th></tr>
|
|
<tr><td><span class="pill">admin</span></td><td><code>users.view</code>, <code>users.create</code>, <code>users.update</code>, <code>users.delete</code></td></tr>
|
|
<tr><td><span class="pill">user</span></td><td>Ninguno extra — gestiona su propio perfil, sus clientes OAuth y sus tokens.</td></tr>
|
|
</table>
|
|
|
|
<h3>Roles por app</h3>
|
|
<p>Independientes del rol global: un admin le puede asignar a cualquier usuario un rol (<code>admin</code> o
|
|
<code>user</code>) <strong>específico de una app registrada</strong>, desde el panel de edición de usuario
|
|
("Roles por app"). Sirve para que, por ejemplo, un usuario sea <code>admin</code> dentro de la app Angular
|
|
pero solo <code>user</code> dentro de la app NestJS — sin tocar su rol global.</p>
|
|
<p>Esa información viaja en la respuesta de la API (<code>app_roles</code>, ver sección 8): cada app ve
|
|
únicamente el rol que ese usuario tiene <em>en ella</em>, resuelto automáticamente según el <code>client_id</code>
|
|
del token usado en la request.</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>7. Idioma (español / inglés / catalán)</h2>
|
|
<p>El selector está arriba a la derecha en la barra de navegación (<strong>ES / EN / CA</strong>). Cambiarlo
|
|
hace un POST a <code>/web-api/locale</code>, 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
|
|
<code>laravel-lang</code>, más los textos propios de la app en <code>lang/{es,en,ca}/messages.php</code>).</p>
|
|
<table>
|
|
<tr><th>Idioma</th><th>Mensajes del servidor</th><th>Interfaz (SPA Vue)</th></tr>
|
|
<tr><td><strong>ES</strong> (español, por defecto)</td><td>✓</td><td>✓</td></tr>
|
|
<tr><td><strong>EN</strong> (inglés)</td><td>✓</td><td>✓ — traducción completa (nav, botones, formularios, tablas, gráficos)</td></tr>
|
|
<tr><td><strong>CA</strong> (catalán)</td><td>✓</td><td>✓ — traducción completa</td></tr>
|
|
</table>
|
|
<p>La interfaz usa <code>vue-i18n</code> con diccionarios en <code>resources/js/i18n/{es,en,ca}.js</code>
|
|
(168 claves cada uno). El locale de arranque del frontend se inyecta desde el servidor
|
|
(<code>window.__LOCALE__</code> en <code>app.blade.php</code>), así que la SPA nace ya en el idioma correcto
|
|
sin parpadeo.</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>8. Cómo conectar tu app (Angular, NestJS, Flutter, etc.)</h2>
|
|
<h3>Paso 1 — Registrar la app</h3>
|
|
<p>Desde <code>/clients</code>, crear un cliente OAuth eligiendo el grant type según el tipo de app:</p>
|
|
<table>
|
|
<tr><th>Tipo de app</th><th>Grant recomendado</th></tr>
|
|
<tr><td>SPA (Angular, React) servida al navegador</td><td>Authorization Code + PKCE, cliente <strong>público</strong></td></tr>
|
|
<tr><td>Backend con secret propio (NestJS, Express)</td><td>Authorization Code, cliente <strong>confidencial</strong>, o Client Credentials si no hay usuario final</td></tr>
|
|
<tr><td>Servicio interno máquina-a-máquina</td><td>Client Credentials</td></tr>
|
|
</table>
|
|
|
|
<h3>Paso 2 — Login del usuario (Authorization Code + PKCE, ej. Angular)</h3>
|
|
<p>Redirigir el navegador a:</p>
|
|
<pre><code>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</code></pre>
|
|
<p>El usuario loguea (o ya tiene sesión) y aprueba el acceso. Passport redirige a <code>redirect_uri</code>
|
|
con <code>?code=...&state=...</code>.</p>
|
|
|
|
<h3>Paso 3 — Canjear el código por un token</h3>
|
|
<pre><code>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}</code></pre>
|
|
|
|
<h3>Alternativa — Client Credentials (ej. NestJS backend-a-backend)</h3>
|
|
<pre><code>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"</code></pre>
|
|
<div class="box">Un token de <code>client_credentials</code> 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.</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>9. API REST completa: construí tu propio panel admin</h2>
|
|
<p>
|
|
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 <strong>admin completo</strong> — no solo consultar usuarios —
|
|
sin depender de la sesión de Laravel. Todos los endpoints requieren <code>Authorization: Bearer {access_token}</code>.
|
|
</p>
|
|
|
|
<h3>Usuarios y roles (scopes <code>users:read</code> / <code>users:write</code>)</h3>
|
|
<table>
|
|
<tr><th>Método</th><th>Ruta</th><th>Descripción</th></tr>
|
|
<tr><td>GET</td><td><code>/api/user</code></td><td>Whoami: usuario del token actual, con <code>roles</code> y <code>app_roles</code>.</td></tr>
|
|
<tr><td>GET</td><td><code>/api/users</code></td><td>Listar usuarios (requiere <code>users.view</code>, salvo client_credentials).</td></tr>
|
|
<tr><td>POST</td><td><code>/api/users</code></td><td>Crear usuario (requiere <code>users.create</code>).</td></tr>
|
|
<tr><td>GET</td><td><code>/api/users/{id}</code></td><td>Ver un usuario. Siempre permitido sobre uno mismo.</td></tr>
|
|
<tr><td>PUT</td><td><code>/api/users/{id}</code></td><td>Editar. Uno mismo puede cambiar nombre/email/contraseña; rol/estado y otros usuarios requieren <code>users.update</code>.</td></tr>
|
|
<tr><td>DELETE</td><td><code>/api/users/{id}</code></td><td>Borrar — soft delete, va a la papelera (requiere <code>users.delete</code>; no se puede autoeliminar).</td></tr>
|
|
<tr><td>GET</td><td><code>/api/users/trashed</code></td><td>Listar usuarios en la papelera (requiere <code>users.view</code>).</td></tr>
|
|
<tr><td>POST</td><td><code>/api/users/{id}/restore</code></td><td>Restaurar un usuario borrado (requiere <code>users.update</code>).</td></tr>
|
|
<tr><td>DELETE</td><td><code>/api/users/{id}/force</code></td><td>Borrado definitivo, sin vuelta atrás (requiere <code>users.delete</code>; el usuario tiene que estar en la papelera).</td></tr>
|
|
<tr><td>GET</td><td><code>/api/users/pending</code></td><td>Listar registros pendientes de aprobación (requiere <code>users.view</code>).</td></tr>
|
|
<tr><td>POST</td><td><code>/api/users/{id}/approve</code></td><td>Aprobar un registro pendiente (requiere <code>users.update</code>).</td></tr>
|
|
<tr><td>GET</td><td><code>/api/users/{id}/app-roles</code></td><td>Roles del usuario por app (propio siempre permitido; otros requieren <code>users.view</code>).</td></tr>
|
|
<tr><td>POST</td><td><code>/api/users/{id}/app-roles</code></td><td>Asignar un rol en una app (requiere <code>users.update</code>, sin excepción para uno mismo).</td></tr>
|
|
<tr><td>DELETE</td><td><code>/api/users/{id}/app-roles/{clientId}/{role}</code></td><td>Quitar un rol en una app (requiere <code>users.update</code>).</td></tr>
|
|
<tr><td>GET</td><td><code>/api/apps</code></td><td>Listar todas las apps registradas (requiere <code>users.view</code>) — para armar el selector de roles-por-app.</td></tr>
|
|
<tr><td>GET</td><td><code>/api/analytics/summary?days=30</code></td><td>KPIs, logins por día/app, usuarios más activos y actividad reciente (requiere <code>users.view</code>).</td></tr>
|
|
</table>
|
|
|
|
<h3>Ejemplo de respuesta de usuario</h3>
|
|
<pre><code>{
|
|
"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"
|
|
}</code></pre>
|
|
<p><code>roles</code> es el rol global; <code>app_roles</code> 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 <code>deleted_at</code> solo aparece (no-null) en las respuestas de
|
|
<code>/api/users/trashed</code>.</p>
|
|
|
|
<h3>Cuenta propia: clientes, tokens y apps autorizadas (scopes <code>account:read</code> / <code>account:write</code>)</h3>
|
|
<p>Equivalente por token a <code>/web-api/clients</code>, <code>/personal-access-tokens</code> y
|
|
<code>/authorized-apps</code> de la sección 4 — siempre sobre el usuario dueño del token (no aplica a
|
|
client_credentials, que no tiene usuario asociado):</p>
|
|
<table>
|
|
<tr><th>Método</th><th>Ruta</th><th>Descripción</th></tr>
|
|
<tr><td>GET</td><td><code>/api/clients</code></td><td>Listar tus clientes OAuth (apps que registraste).</td></tr>
|
|
<tr><td>POST</td><td><code>/api/clients</code></td><td>Registrar un cliente nuevo (devuelve <code>plain_secret</code> una sola vez).</td></tr>
|
|
<tr><td>PUT</td><td><code>/api/clients/{id}</code></td><td>Editar nombre/redirect URIs.</td></tr>
|
|
<tr><td>DELETE</td><td><code>/api/clients/{id}</code></td><td>Revocar el cliente y sus tokens.</td></tr>
|
|
<tr><td>GET</td><td><code>/api/personal-access-tokens</code></td><td>Listar tus Personal Access Tokens.</td></tr>
|
|
<tr><td>POST</td><td><code>/api/personal-access-tokens</code></td><td>Crear uno (el token se muestra una sola vez).</td></tr>
|
|
<tr><td>DELETE</td><td><code>/api/personal-access-tokens/{id}</code></td><td>Revocarlo.</td></tr>
|
|
<tr><td>GET</td><td><code>/api/authorized-apps</code></td><td>Apps de terceros a las que le diste acceso.</td></tr>
|
|
<tr><td>DELETE</td><td><code>/api/authorized-apps/{tokenId}</code></td><td>Revocarle el acceso a una.</td></tr>
|
|
</table>
|
|
|
|
<h3>Scopes disponibles</h3>
|
|
<table>
|
|
<tr><th>Scope</th><th>Permite</th></tr>
|
|
<tr><td><code>users:read</code></td><td>Leer usuarios y roles por app</td></tr>
|
|
<tr><td><code>users:write</code></td><td>Crear, editar y borrar usuarios; asignar roles por app</td></tr>
|
|
<tr><td><code>account:read</code></td><td>Leer tus clientes OAuth, tokens y apps autorizadas</td></tr>
|
|
<tr><td><code>account:write</code></td><td>Gestionar tus clientes OAuth, tokens y apps autorizadas</td></tr>
|
|
</table>
|
|
<p>No hay scope por defecto: cada app debe pedir explícitamente los que necesita al iniciar el flujo OAuth
|
|
(ej. <code>scope=users:read users:write account:read account:write</code> para un admin completo).</p>
|
|
<div class="box warn">
|
|
<strong>Client credentials y recursos self-service:</strong> un token <code>client_credentials</code> no
|
|
tiene usuario asociado, así que <code>/api/clients</code>, <code>/api/personal-access-tokens</code> y
|
|
<code>/api/authorized-apps</code> le devuelven <code>422</code> (no hay "cuenta propia" que gestionar).
|
|
Sí puede usar <code>/api/users</code> y <code>/api/apps</code> como servicio de confianza.
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>10. Documentación interactiva (Swagger)</h2>
|
|
<p>Disponible en <code>http://localhost:8000/api/documentation</code>. Incluye el botón <strong>Authorize</strong>
|
|
para probar los tres flujos (Authorization Code, Client Credentials, Password) directamente desde el navegador,
|
|
usando el cliente de ejemplo <em>"Swagger UI"</em> sembrado (sección 3).</p>
|
|
<p>El JSON crudo de la especificación OpenAPI está en <code>/docs</code>.</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>11. Referencia rápida de rutas</h2>
|
|
<h3>Interfaz web (sesión, uso interno de esta app)</h3>
|
|
<table>
|
|
<tr><th>Ruta</th><th>Qué hace</th></tr>
|
|
<tr><td><code>POST /web-api/register</code></td><td>Registro — crea la cuenta pendiente, no loguea.</td></tr>
|
|
<tr><td><code>POST /web-api/login</code> / <code>/logout</code></td><td>Autenticación por sesión.</td></tr>
|
|
<tr><td><code>GET /web-api/me</code></td><td>Usuario logueado (con roles/permisos).</td></tr>
|
|
<tr><td><code>GET/POST/PUT/DELETE /web-api/clients</code></td><td>Clientes OAuth propios.</td></tr>
|
|
<tr><td><code>GET/POST/DELETE /web-api/personal-access-tokens</code></td><td>Personal Access Tokens propios.</td></tr>
|
|
<tr><td><code>GET/DELETE /web-api/authorized-apps</code></td><td>Apps de terceros autorizadas.</td></tr>
|
|
<tr><td><code>GET/POST/PUT/DELETE /web-api/admin/users</code></td><td>Administración de usuarios (admin).</td></tr>
|
|
<tr><td><code>GET /web-api/admin/users/trashed</code></td><td>Usuarios en la papelera (admin).</td></tr>
|
|
<tr><td><code>POST /web-api/admin/users/{id}/restore</code></td><td>Restaurar un usuario borrado (admin).</td></tr>
|
|
<tr><td><code>DELETE /web-api/admin/users/{id}/force</code></td><td>Borrado definitivo (admin).</td></tr>
|
|
<tr><td><code>GET /web-api/admin/users/pending</code></td><td>Registros pendientes de aprobación (admin).</td></tr>
|
|
<tr><td><code>POST /web-api/admin/users/{id}/approve</code></td><td>Aprobar un registro pendiente (admin).</td></tr>
|
|
<tr><td><code>GET /web-api/admin/apps</code></td><td>Listado de todas las apps registradas (admin).</td></tr>
|
|
<tr><td><code>GET/POST/DELETE /web-api/admin/users/{id}/app-roles</code></td><td>Roles por app de un usuario (admin).</td></tr>
|
|
<tr><td><code>GET /web-api/admin/analytics/summary</code></td><td>Sesiones, logins y actividad (admin).</td></tr>
|
|
<tr><td><code>POST /web-api/locale</code></td><td>Cambiar idioma (es/en/ca).</td></tr>
|
|
</table>
|
|
|
|
<h3>OAuth2 (Passport)</h3>
|
|
<table>
|
|
<tr><th>Ruta</th><th>Qué hace</th></tr>
|
|
<tr><td><code>GET /oauth/authorize</code></td><td>Inicio del flujo Authorization Code (pantalla de consentimiento).</td></tr>
|
|
<tr><td><code>POST /oauth/token</code></td><td>Emisión/renovación de tokens (todos los grant types).</td></tr>
|
|
</table>
|
|
|
|
<h3>API REST (Bearer token, para apps externas — Angular, Flutter, lo que sea)</h3>
|
|
<table>
|
|
<tr><th>Ruta</th><th>Qué hace</th></tr>
|
|
<tr><td><code>GET /api/user</code></td><td>Whoami.</td></tr>
|
|
<tr><td><code>GET/POST/PUT/DELETE /api/users</code></td><td>Gestión de usuarios (DELETE = papelera).</td></tr>
|
|
<tr><td><code>GET /api/users/trashed</code></td><td>Usuarios en la papelera.</td></tr>
|
|
<tr><td><code>POST /api/users/{id}/restore</code></td><td>Restaurar un usuario borrado.</td></tr>
|
|
<tr><td><code>DELETE /api/users/{id}/force</code></td><td>Borrado definitivo.</td></tr>
|
|
<tr><td><code>GET /api/users/pending</code></td><td>Registros pendientes de aprobación.</td></tr>
|
|
<tr><td><code>POST /api/users/{id}/approve</code></td><td>Aprobar un registro pendiente.</td></tr>
|
|
<tr><td><code>GET/POST/DELETE /api/users/{id}/app-roles</code></td><td>Roles por app de un usuario.</td></tr>
|
|
<tr><td><code>GET /api/apps</code></td><td>Listado de apps registradas.</td></tr>
|
|
<tr><td><code>GET /api/analytics/summary</code></td><td>Sesiones, logins y actividad.</td></tr>
|
|
<tr><td><code>GET/POST/PUT/DELETE /api/clients</code></td><td>Tus clientes OAuth.</td></tr>
|
|
<tr><td><code>GET/POST/DELETE /api/personal-access-tokens</code></td><td>Tus Personal Access Tokens.</td></tr>
|
|
<tr><td><code>GET/DELETE /api/authorized-apps</code></td><td>Apps de terceros que autorizaste.</td></tr>
|
|
</table>
|
|
|
|
<footer class="pagefoot">OAuth2 Identity Server — Manual de uso · generado automáticamente a partir del estado actual del proyecto.</footer>
|
|
</section>
|
|
|
|
</body>
|
|
</html>
|