symfony/security-http
Symfony Security HTTP integrates the Security Core component into HTTP applications, letting you secure parts of your app with firewalls and authenticate visitors through configurable authenticators. It bridges security policies and incoming requests.
Install only if you actually need Symfony’s HTTP security engine:
composer require symfony/security-http
In a normal Laravel app, start with Laravel’s native auth first. This package is useful only when you need a Symfony authenticator/firewall feature that Laravel’s guard system does not already provide, such as a specific OIDC authenticator, login-link flow, or a custom Symfony AuthenticatorInterface implementation.
Version check first:
5.4.x line, upgrade to at least v5.4.54; it is a security release.8.x line and use OIDC discovery or Symfony security event listeners, use v8.1.8+ for the OIDC issuer-binding and event-dispatcher type fixes.Look first at:
Symfony\Component\Security\Http\Authenticator\AuthenticatorInterfaceAbstractAuthenticatorPassport, UserBadge, PasswordCredentialsSymfony\Component\Security\Http\Firewall / FirewallMapAuth::extend() and custom guard supportFirst use case: wrap a Symfony authenticator behind a Laravel guard or controller flow, then hand the authenticated user back to Laravel’s Auth system. Do not try to copy Symfony SecurityBundle firewall configuration into Laravel; Laravel does not expose security.firewall_map out of the box.
Use Laravel as the application auth layer and Symfony’s authenticator as a focused login mechanism:
AbstractAuthenticator.supports(), decide whether the request is an API-key, OIDC, login-link, or custom login request.authenticate(), return a Passport with a UserBadge and any credentials badges.Auth::login($user) if needed.Example authenticator shape:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class CustomLoginAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return $request->isMethod('POST') && $request->getPathInfo() === '/login';
}
public function authenticate(Request $request): Passport
{
$email = (string) $request->request->get('email');
$password = (string) $request->request->get('password');
return new Passport(
new UserBadge($email, fn (string $id) => User::where('email', $id)->firstOrFail()),
[new PasswordCredentials($password)]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
Auth::login($token->getUser());
return redirect('/dashboard');
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
return redirect('/login')->withErrors([
'email' => $exception->getMessage(),
]);
}
}
For Laravel applications, keep authorization in Laravel Gates/Policies where possible. Symfony’s #[IsGranted] attribute and voters are not automatically enforced by Laravel middleware. If you use Symfony voters, adapt them through a Laravel Gate or a custom middleware.
Use Symfony voters only when the authorization decision is tightly coupled to a Symfony security flow or when you are already passing Symfony tokens through the stack.
Symfony firewalls and RequestMatcher instances are powerful but low-level. In Laravel, prefer:
If you do use Symfony RequestMatcher, keep the matching rules explicit and test them against Laravel route edge cases, including trailing slashes, HEAD requests, and API prefixes.
If using OIDC discovery:
8.x lines, use v8.1.8+ if you use discovered OIDC keys, because discovered keys are now bound to issuers.If using Symfony login links or persistent remember-me handling:
400/redirect, not a TypeError or 500.v5.4.54 narrows the max_uses race, but do not rely on max_uses alone for high-security flows if you can atomically consume the link in your own storage.v5.4.54 makes PersistentRememberMeHandler tolerate malformed cookies; still test malformed/tampered cookie values in Laravel feature tests.Test the authenticator directly first:
supports() returns correct values for matching and non-matching requests.authenticate() builds the expected Passport.AuthenticationException or response.Auth::login().Then add Laravel feature tests for the route, middleware, CSRF behavior, redirects, and JSON responses. For security-sensitive flows, also test:
The biggest pitfall is assuming Laravel exposes Symfony services such as security.firewall_map, security.token_storage, or security.authentication_utils automatically. It generally does not. You must wire those services yourself or keep Symfony’s authenticator limited to a controller/service boundary.
Symfony authenticators expect a Symfony UserInterface-compatible user. Laravel’s Authenticatable model is not automatically compatible. Use an adapter, implement the needed Symfony interface, or resolve the Laravel user inside your integration layer before calling Auth::login().
Laravel’s VerifyCsrfToken middleware is the normal CSRF layer. Symfony’s CSRF attributes/helpers are not automatically enforced in Laravel routes. Do not assume #[IsCsrfTokenValid] protects a Laravel endpoint unless you explicitly integrate it. Also verify HEAD/OPTIONS behavior for any custom CSRF checks.
v5.4.54 is a security release for the 5.4.x line. Upgrade if you are pinned there. The release notes cover four relevant fixes:
PersistentRememberMeHandler receives a malformed cookie.TypeError.max_uses.Laravel-specific implications: audit your auth logging so tokens/cookies are not recorded; return clean validation responses for malformed login-link input; test malformed remember-me cookies explicitly; and keep your own one-time-use enforcement for high-security login links because the max_uses race is narrowed, not eliminated.
On newer 8.x lines, v8.1.8 includes a fix binding discovered OIDC keys to issuers. If you use OIDC discovery, upgrade and confirm your issuer configuration is correct. This matters when multiple issuers or discovery documents are involved.
On newer 8.x lines, v8.1.8 also fixes the type declared for the event dispatcher. If you register Symfony security event listeners, ensure your listener signatures and service definitions match the dispatcher interface expected by the installed Symfony version.
If you use remember-me cookies or serialize Symfony tokens, lazy-loaded users can fail during serialization. Initialize or resolve lazy user properties before token serialization, and test remember-me flows explicitly. Also test malformed cookie handling after upgrading to v5.4.54+.
If you use multiple Symfony firewalls/matchers, order matters. API-style matchers should usually run before broad web matchers. For debugging, inspect the current Symfony token from the token storage service, but remember that Laravel’s Auth::user() may be a separate source of truth unless you bridge them.
Useful extension points:
AuthenticatorInterface / AbstractAuthenticator for API keys, OIDC, JWT, login links, or legacy login.UserBadge loaders for mapping identifiers to Laravel users.How can I help you explore Laravel packages today?