Guards & Resolvers
A guard authenticates an incoming request and returns a typed value — usually the authenticated user or tenant — which is then injected directly into your controller method. If the guard throws, the request is rejected before anything else runs.
A resolver extracts and shapes request data into a typed value — query params, headers, or anything that isn’t about authentication.
Writing a Guard
Section titled “Writing a Guard”A guard implements the Guard interface. It can have constructor dependencies — the container autowires them:
use Antares\Http\Guards\Guard;use Antares\Exceptions\HttpException;use Psr\Http\Message\ServerRequestInterface;
final class JwtGuard implements Guard{ public function __construct( private readonly JwtService $jwt, ) {}
public function resolve(ServerRequestInterface $request): AuthUser { $header = $request->getHeaderLine('Authorization'); $token = ltrim($header, 'Bearer '); $user = $this->jwt->decode($token);
if ($user === null) { throw new HttpException(401, 'Unauthorized'); }
return $user; }}Using a Guard
Section titled “Using a Guard”Apply #[Guards(GuardClass::class)] to the parameter that should receive the resolved value:
use Antares\Http\Attributes\Guards;
class PostController{ #[Post('/posts', 201)] public function store( #[Guards(JwtGuard::class)] AuthUser $user, CreatePostRequest $request, ): PostResponse { return new PostResponse( id: 1, title: $request->title, authorId: $user->id, ); }}The guard runs first. If it throws, the DTO is never hydrated and the controller method is never called.
Multiple Guards
Section titled “Multiple Guards”Different routes can use different guards. You can also stack multiple guards on a single method:
class ReportController{ #[Get('/reports')] public function index( #[Guards(TenantGuard::class)] Tenant $tenant, #[Guards(JwtGuard::class)] AuthUser $user, ): array { return ['tenant' => $tenant->id, 'user' => $user->id]; }}Guards run in the order they appear in the parameter list.
Role-Based Access
Section titled “Role-Based Access”Guards are a natural place to enforce roles. Throw an HttpException with 403 if the resolved user does not have the required access:
final class AdminGuard implements Guard{ public function __construct( private readonly JwtService $jwt, ) {}
public function resolve(ServerRequestInterface $request): AuthUser { $user = $this->jwt->decode( ltrim($request->getHeaderLine('Authorization'), 'Bearer ') );
if ($user === null) { throw new HttpException(401, 'Unauthorized'); }
if (!$user->isAdmin()) { throw new HttpException(403, 'Forbidden'); }
return $user; }}#[Delete('/users/{id}', 204)]public function destroy( #[Guards(AdminGuard::class)] AuthUser $user, int $id,): void { // only admins reach here}Guards Are Request-Scoped
Section titled “Guards Are Request-Scoped”Guards always receive the ServerRequestInterface and are designed to authenticate based on request data — headers, cookies, tokens. If you need logic that has nothing to do with the current request, that belongs in a plain service resolved by the container, not a guard.
Resolvers
Section titled “Resolvers”A resolver extracts and shapes request data into a typed value — query params, headers, or anything that isn’t about authentication. Unlike guards, resolvers never throw auth errors; they just parse and return.
use Antares\Http\Resolvers\Resolver;use Psr\Http\Message\ServerRequestInterface;
final class PaginationResolver implements Resolver{ public function resolve(ServerRequestInterface $request): Pagination { $params = $request->getQueryParams();
return new Pagination( page: (int) ($params['page'] ?? 1), perPage: (int) ($params['per_page'] ?? 15), ); }}Apply #[Inject(ResolverClass::class)] to the parameter:
use Antares\Http\Attributes\Inject;
class PostController{ #[Get('/posts')] public function index( #[Guards(JwtGuard::class)] AuthUser $user, #[Inject(PaginationResolver::class)] Pagination $page, ): array { ... }}Resolvers can have constructor dependencies — the container autowires them just like guards.
Guards vs Resolvers
Section titled “Guards vs Resolvers”#[Guards] | #[Inject] | |
|---|---|---|
| Interface | Guard | Resolver |
| Purpose | Auth / access control | Anything |
| Throws | HttpException to reject | Up to you |
| Returns | Authenticated principal | Whatever you need |