Sanctum comprueba el token en cada petición. Eso cuesta una consulta para el token, una consulta para su consumidor y una escritura en last_used_at. También tiene una consecuencia que la gente subestima: la revocación es inmediata.
$consumer->tokens()->where('name', 'production')->delete();
La siguiente petición con ese token recibe un 401. No hay que esperar a que caduque ninguna caché.
Yo solía cachear las búsquedas de tokens para ahorrarme esas consultas, y tenía que escribir párrafos explicando cuánto tiempo sigue siendo válido un token revocado. Ese es el precio. No lo hagas hasta haber medido que la autenticación es tu cuello de botella, y el Capítulo 9 trata de cómo medir.
Rotar es emitir un token nuevo antes de que el viejo caduque, y borrar el viejo una vez que el consumidor ha cambiado. Como un consumidor puede tener varios tokens, los dos se solapan y nada se cae.
Los tokens caducados dejan de funcionar solos, pero sus filas se quedan. Programa el comando de Sanctum que las limpia:
// routes/console.php
Schedule::command('sanctum:prune-expired')->daily();
Y mira last_used_at de vez en cuando. Un token que no se ha usado en tres meses pertenece a un servicio que ya no existe.
Probarlo
El helper de test de Sanctum autentica un modelo con un conjunto de habilidades, sin crear un token real:
// tests/Feature/AuthTest.php
it('rejects a request without a token', function () {
$this->getJson('/api/licenses')->assertUnauthorized();
});
it('forbids writing with a read-only token', function () {
Sanctum::actingAs(
Consumer::factory()->create(),
['licenses:read'],
);
$this->postJson('/api/licenses', [
'name' => 'Acme',
'domain' => 'acme.test',
])->assertForbidden();
});
Escribe un test del segundo tipo para cada habilidad que definas. Hasta que exista uno así, nadie ha comprobado que la habilidad haga algo.
Dale a actingAs exactamente las habilidades de las que trata el test, nunca ['*']. Un comodín pasa todas las comprobaciones, así que un test que lo usa no demuestra nada sobre la autorización.
Aquí corresponde un test más, porque protege la afirmación sobre la que descansa este capítulo. Comprueba que la petición al proveedor se hace con las credenciales de quien llama y no con las de otro:
it('calls the provider as the consumer', function () {
Http::fake(['*' => Http::response(['data' => []])]);
$consumer = Consumer::factory()
->forPartner('partner-token')
->create();
Sanctum::actingAs($consumer, ['licenses:read']);
$this->getJson('/api/licenses')->assertOk();
Http::assertSent(fn ($request) => $request->hasHeader(
'Authorization',
'Bearer partner-token',
));
});
forPartner() es un estado de la factory que pone un token de proveedor en settings. El Capítulo 12 lo muestra. De aquí en adelante, los tests de este libro que llaman a una ruta protegida se autentican primero, normalmente en un beforeEach. Eso incluye el test del Capítulo 2, que ahora necesita a alguien que llame antes de poder llegar a la validación.
Cuándo no usar esto
- Una aplicación de navegador propia. Usa la autenticación SPA de Sanctum basada en cookies, no tokens en el almacenamiento local.
- Desarrolladores externos que necesitan actuar en nombre de tus usuarios. Eso es OAuth, y Laravel Passport lo implementa.
- Una API pública y anónima. Sáltate la autenticación y apóyate en el límite de peticiones del Capítulo 6.
Resumen del capítulo 4
- Autentica los servicios con tokens de API de Sanctum sobre un modelo propio. Un modelo con tokens no tiene por qué ser una persona.
- Los tokens se guardan como hash. Pasa siempre habilidades y una caducidad, y nunca emitas
*. auth:sanctumen el grupo de rutas.$request->user()es el consumidor.- Declara las habilidades en el controlador, cerrado por defecto. Un controlador nuevo está abierto hasta que lo cierras.
- Las credenciales por inquilino fallan en cerrado. Sin valor de reserva compartido.
- Todo lo que depende de quién pregunta es
scoped, no un singleton. - No caches la autenticación hasta haberla medido. La revocación inmediata vale lo que cuestan las consultas.