Autenticación — Recetas y buenas prácticas
La autenticación en Osumi Framework 9.10 se construye normalmente mediante:
- Un endpoint de login que valida credenciales y emite un token.
- Un Middleware
beforeque valida el token en las rutas protegidas. - Contexto de Middleware para valores autenticados de confianza.
- DTOs para entrada validada y campos procedentes de Middleware.
- Servicios para lógica reutilizable de autenticación y autorización.
1. Proteger rutas con Middleware
Ejemplo:
ORoute::get(
'/profile',
ProfileComponent::class,
[
OMiddleware::PHASE_BEFORE => [
LoginMiddleware::class
]
]
);
Cuando la ruta coincide:
- Se ejecutan los Middlewares
before. LoginMiddlewarevalida la petición.- Si devuelve
stop => true, se omiten los Middlewaresbeforerestantes y el componente. afterResponsesigue ejecutándose.- El framework emite la respuesta de error tipada.
Los fallos de autenticación deberían utilizar normalmente HTTP 401.
2. Login Middleware
Un Middleware de login debe publicar contexto de confianza cuando la validación tiene éxito.
<?php
declare(strict_types=1);
namespace Osumi\OsumiFramework\App\Middleware;
use Osumi\OsumiFramework\Core\OMiddleware;
final class LoginMiddleware {
/**
* Validate authentication and publish user context.
*
* @param string $phase Current middleware phase.
* @param array<string, mixed> $data Current middleware pipeline data.
*
* @return array<string, mixed> Middleware result.
*/
public static function handle(
string $phase,
array $data
): array {
if ($phase !== OMiddleware::PHASE_BEFORE) {
return [];
}
// Validar aquí el token Authorization.
$is_valid = true;
$id_user = 42;
$role = 'admin';
if (!$is_valid) {
return [
'stop' => true,
'status_code' => 401,
'message' => 'Unauthorized'
];
}
return [
'context' => [
'id' => $id_user,
'role' => $role
]
];
}
}
LoginMiddleware se expone públicamente como Login.
3. Crear el endpoint de login
Un endpoint de login normalmente:
- Recibe las credenciales mediante un DTO.
- Las valida mediante un servicio.
- Crea un token.
- Devuelve el token al cliente.
- El cliente envía el token mediante
Authorization.
DTO de ejemplo:
class LoginDTO extends ODTO {
#[ODTOField(required: true)]
public ?string $email = null;
#[ODTOField(required: true)]
public ?string $password = null;
}
Servicio de autenticación:
class AuthService extends OService {
/**
* Validate credentials and return a token.
*
* @param string $email User email.
* @param string $password Plain-text password.
*
* @return array{token: string}|null Token data or null on failure.
*/
public function login(
string $email,
string $password
): ?array {
$user = User::findOne([
'email' => $email
]);
if (
$user === null ||
!password_verify(
$password,
$user->password
)
) {
return null;
}
$token = new OToken(
$this->getConfig()->getExtra('secret')
);
$token->addParam(
'id',
$user->id
);
return [
'token' => $token->getToken()
];
}
}
4. Leer contexto de Middleware desde componentes
Obtener todo el contexto:
$login = $req->getMiddleware(
'Login'
);
Obtener un único valor:
$id_user = $req->getMiddlewareValue(
'Login',
'id'
);
Es preferible usar getMiddlewareValue() cuando solo necesitas un valor.
5. Usar contexto de Middleware en DTOs
Los DTOs pueden usar contexto de Middleware como origen explícito:
#[ODTOField(
required: true,
middleware: 'Login',
middlewareProperty: 'id'
)]
public ?int $idUser = null;
Esto evita que el cliente pueda suplantar el ID del usuario autenticado.
Si el valor de contexto no existe, el valor del DTO permanece en null; no se recurre a los datos enviados por el cliente.
6. Endpoint protegido usando un DTO
Ruta:
ORoute::get(
'/my-cinemas',
GetCinemasComponent::class,
[
OMiddleware::PHASE_BEFORE => [
LoginMiddleware::class
]
]
);
DTO:
class GetCinemasDTO extends ODTO {
#[ODTOField(
required: true,
middleware: 'Login',
middlewareProperty: 'id'
)]
public ?int $idUser = null;
}
Componente:
class GetCinemasComponent extends OComponent {
private ?CinemaService $cinema_service = null;
/**
* Prepare the component.
*/
public function __construct() {
parent::__construct();
$this->cinema_service = inject(
CinemaService::class
);
}
/**
* Load cinemas for the authenticated user.
*
* @param GetCinemasDTO $dto Authenticated request DTO.
*
* @return void
*/
public function run(GetCinemasDTO $dto): void {
if (
!$dto->isValid() ||
$dto->idUser === null
) {
return;
}
$this->list = $this->cinema_service->getCinemas(
$dto->idUser
);
}
}
7. Permisos
Un Middleware puede publicar información de rol o permisos:
return [
'context' => [
'id' => 42,
'role' => 'admin'
]
];
Un componente puede consultarla:
$role = $req->getMiddlewareValue(
'Login',
'role'
);
Para reglas de autorización compartidas por muchas rutas, es preferible usar un Middleware específico de autorización en lugar de repetir las comprobaciones en los componentes.
8. Cerrar sesión
En una autenticación stateless basada en tokens, cerrar sesión normalmente consiste en eliminar el token en el cliente.
Si se necesita revocación en servidor, almacena identificadores de tokens revocados en un servicio/caché y haz que el Middleware de autenticación los rechace.
9. Buenas prácticas
- Usa Middlewares
beforepara autenticación. - Devuelve HTTP 401 cuando falle la autenticación.
- Publica únicamente el contexto de confianza necesario.
- Nunca confíes en IDs de usuario autenticado enviados por el cliente.
- Usa fuentes de Middleware en DTOs para valores autenticados.
- Mantén la lógica de negocio en servicios.
- Usa un Middleware de autorización separado cuando los permisos se compartan entre rutas.
- Mantén los secretos de token en configuración.
10. Resumen
Un flujo de autenticación habitual contiene:
- Endpoint de login.
- Servicio que emite tokens.
LoginMiddlewareen rutas protegidas.- Contexto de Middleware de confianza.
- DTOs y/o
ORequestque consumen ese contexto. - Servicios que contienen la lógica de negocio.
Este es el reemplazo canónico, introducido en 9.9, del antiguo flujo de autenticación basado en Filters.