Osumi Framework
es en eu v9.12.1 GitHub

Autenticación — Recetas y buenas prácticas

La autenticación en Osumi Framework 9.10 se construye normalmente mediante:


1. Proteger rutas con Middleware

Ejemplo:

ORoute::get(
	'/profile',
	ProfileComponent::class,
	[
		OMiddleware::PHASE_BEFORE => [
			LoginMiddleware::class
		]
	]
);

Cuando la ruta coincide:

  1. Se ejecutan los Middlewares before.
  2. LoginMiddleware valida la petición.
  3. Si devuelve stop => true, se omiten los Middlewares before restantes y el componente.
  4. afterResponse sigue ejecutándose.
  5. 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:

  1. Recibe las credenciales mediante un DTO.
  2. Las valida mediante un servicio.
  3. Crea un token.
  4. Devuelve el token al cliente.
  5. 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


10. Resumen

Un flujo de autenticación habitual contiene:

  1. Endpoint de login.
  2. Servicio que emite tokens.
  3. LoginMiddleware en rutas protegidas.
  4. Contexto de Middleware de confianza.
  5. DTOs y/o ORequest que consumen ese contexto.
  6. 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.