Auth middleware — protecting routes
Concept
Auth middleware protects routes by verifying the current request is authenticated before the controller runs. It's a guard in the middleware pipeline.
How auth middleware works:
- Resolve the current guard (session or token, based on configuration).
- Check if the request is authenticated via
$guard->check(). - If YES: call
$next($request)— continue to the next middleware/controller. - If NO: return 401 Unauthorized (API) or redirect to login (web).
Guard resolution: Different route groups use different guards. API routes use the token guard. Web routes use the session guard. The middleware knows which guard to use via configuration or a constructor argument.
auth vs auth:api: Named middleware with parameters. auth uses the default (web) guard. auth:api uses the api guard explicitly.
Passing user to downstream handlers: After verifying authentication, the auth middleware can attach the user to the request: $request = $request->withAttribute('user', $guard->user()). Controllers then access $request->getAttribute('user') instead of calling the auth system.
Redirecting vs responding 401: For web (browser) requests, redirect to the login page. For API (JSON) requests, return a 401 JSON response. Detect via Accept: application/json header or route group.
Code Example
<?php
namespace Framework\Auth\Middleware;
use Framework\Auth\AuthManagerInterface;
use Framework\Http\Request;
use Framework\Http\Response;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Authenticate implements MiddlewareInterface
{
public function __construct(
private readonly AuthManagerInterface $auth,
private readonly string $guard = 'web',
) {}
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
$guard = $this->auth->guard($this->guard);
if ($guard->guest()) {
return $this->unauthenticated($request);
}
// Attach user to request for downstream handlers
$request = $request->withAttribute('user', $guard->user());
$request = $request->withAttribute('auth', $guard);
return $next->handle($request);
}
private function unauthenticated(ServerRequestInterface $request): ResponseInterface
{
// API requests → 401 JSON
if ($this->expectsJson($request)) {
return new \Nyholm\Psr7\Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => 'Unauthenticated.'])
);
}
// Web requests → redirect to login
return new \Nyholm\Psr7\Response(302, ['Location' => '/login']);
}
private function expectsJson(ServerRequestInterface $request): bool
{
$accept = $request->getHeaderLine('Accept');
return str_contains($accept, 'application/json') || str_contains($accept, 'application/vnd.api');
}
}
// Using in a middleware with named guard
class AuthenticateWithGuard implements MiddlewareInterface
{
public function __construct(
private readonly AuthManagerInterface $auth,
private readonly string $guardName,
) {}
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
// Same as above but uses $this->guardName
$guard = $this->auth->guard($this->guardName);
if ($guard->guest()) {
return new \Nyholm\Psr7\Response(401, [], 'Unauthorized');
}
return $next->handle($request->withAttribute('user', $guard->user()));
}
}
// Controller using the injected user
class UserController
{
public function me(ServerRequestInterface $request): ResponseInterface
{
$user = $request->getAttribute('user'); // set by auth middleware
return new \Framework\Http\JsonResponse($user->toArray());
}
}