El paquete bearer de Ryan Chandler proporciona un sistema mínimo de autenticación por token: un modelo base Token, hashing seguro, gestión de expiración y un middleware VerifyBearerToken. Cubre la necesidad fundamental: buscar un token en la cabecera Authorization: Bearer ..., verificar que existe en la base de datos y autenticar la petición. Eso es todo. Sin conceptos de usuario, sin sesiones, sin relaciones con un modelo User. Perfecto para APIs de servicio a servicio.
Pero el modelo base Token es intencionadamente genérico:
token: la cadena del token, hasheadadomains:AsArrayObject::classexpires_at: marca de expiración- Marcas de tiempo (
created_at,updated_at)
Eso cubre la validación básica y nada más. Para nuestra API multiinquilino necesitamos campos adicionales que el paquete no asume. Extender el modelo base es el enfoque correcto: heredamos todo el endurecimiento de seguridad del paquete (hashing, validación, middleware) y añadimos nuestras propias propiedades.
Por qué extendemos en lugar de construir desde cero:
Endurecimiento de seguridad. El paquete hace bien el hashing de tokens con bcrypt (seguro, con sal, resistente a ataques de tiempo). Construirlo nosotros introduce riesgo.
Middleware probado. El middleware VerifyBearerToken del paquete está curtido. Lo extendemos con lógica propia de validación de dominio en lugar de reinventar la autenticación.
Convención. El paquete sigue patrones de Laravel. Nuestro modelo Bearer usa características estándar de Eloquent (casts, relaciones, factories).
Superficie de API mínima. El paquete es pequeño (un modelo, un middleware). No estamos peleando con un framework complejo: estamos extendiendo simplicidad.
Este es nuestro modelo Bearer extendiendo el Token del paquete:
// Lives in the shared infrastructure package, not in any one service — every
// service authenticates against the same model rather than its own copy.
final class Bearer extends Token
{
/** @use HasFactory<BearerFactory> */
use HasFactory;
protected function casts(): array
{
return [
'domains' => 'array',
'expires_at' => 'datetime',
'settings' => 'array',
];
}
protected static function newFactory(): BearerFactory
{
return new BearerFactory;
}
}
Los tres casts soportan peso. settings y domains son los dos campos que el paquete desconoce, y expires_at tiene que ser un objeto de fecha o toda comparación de expiración del código estará comparando cadenas en silencio.
Pon el modelo en el paquete compartido, no en el servicio. La autenticación es lo único que todos los servicios de una flota hacen de forma idéntica, y una copia por servicio es una oportunidad por servicio de que las cosas se separen.
Cuatro propiedades importan de verdad en nuestro modelo:
token (heredada). La cadena del token. El paquete la hashea con bcrypt al crearla. Cuando un cliente envía Authorization: Bearer abc123xyz, el paquete extrae abc123xyz y lo compara con la versión hasheada aquí. Es seguro porque nunca almacenamos ni comparamos tokens en claro: sólo persisten los hashes.
expires_at (heredada). Cuándo muere el token. Nos aseguramos de que esté convertida a objeto de fecha para poder comparar. Los tokens son tu mecanismo de revocación: deja que expiren en lugar de borrarlos activamente.
domains (heredada). Lista opcional de dominios permitidos. La añadimos para restringir el uso del token a orígenes concretos. Si el token del Socio A sólo funciona desde partner-a.com, un atacante que lo robe y lo use desde attacker.com será rechazado. Seguridad multisocio en un campo.
settings (campo propio). Propiedad JSON con la configuración por token. Aquí vive la multiinquilinidad. Cada token lleva su propia elección de driver y sus credenciales:
{
"license": {
"driver": "statamic",
"token": "statamic_abc123_secret"
}
}
El token del Socio A dice «usa Statamic con mis credenciales». El del Socio B dice «usa Statamic con las suyas». La misma API, backends distintos, sin tablas de inquilinos, sin enrutado complejo. La configuración va horneada en el token.
Por qué este diseño
El paquete nos da seguridad. Nosotros añadimos flexibilidad. Al extender en lugar de reemplazar, obtenemos:
- Seguridad por herencia: hashing, comparaciones resistentes a ataques de tiempo, patrones probados
- Flexibilidad por extensión: campos propios para nuestros requisitos concretos
- Testabilidad: las factories crean tokens con distintas configuraciones
- Auditoría: las marcas de tiempo registran cuándo se crearon
// In code or tests
$bearer = Bearer::factory()
->withDomains(['https://partner-a.com'])
->create();
// Behind the scenes:
// - Package hashes the raw token
// - Our custom model adds domains and settings
// - Factories handle realistic test data
El paquete responde a «¿este token es real?». Nuestro modelo responde a «¿qué puede hacer este token?».