jmikola/auto-login-bundle
A Symfony bundle that enables passwordless automatic login using secure tokens, letting users authenticate via specially crafted URLs. Integrates with Symfony’s security system for seamless sign-in from emails, links, or trusted applications.
What it does: adds an auto_login firewall listener that looks for a signed token in a query parameter (autologin by default). If the token verifies, the user is loaded through the firewall's user provider and authenticated for that request — perfect for newsletter links, magic links, one-click unsubscribe/preferences, etc.
Reality check before you commit: the bundle is a thin integration layer over Symfony's legacy security factory system (SecurityFactoryInterface, security.authentication.factory). It works cleanly on Symfony 3.4/4.4, will emit deprecations on 5.3/5.4, and will not load on Symfony 6.x. If you are on 5.3+, use the built-in login_link firewall + LoginLinkHandlerInterface instead — it is the supported successor to this bundle. Pick this package only if you're pinned to an older Symfony.
Install and wire up:
composer require jmikola/auto-login-bundle
// config/bundles.php
Jmikola\AutoLoginBundle\JmikolaAutoLoginBundle::class => ['all' => true],
# config/packages/jmikola_auto_login.yaml
jmikola_auto_login:
user_identifier: email # property the token carries / provider looks up by
# lifetime: 86400 # optional token expiry, in seconds
# config/packages/security.yaml
security:
providers:
users:
entity:
class: App\Entity\User
property: email # MUST match user_identifier above
firewalls:
main:
pattern: ^/
provider: users
auto_login: true
form_login:
login_path: app_login
check_path: app_login
logout: ~
First use case — generate a link anywhere you can inject the token generator:
use Jmikola\AutoLoginBundle\Security\TokenGeneratorInterface;
public function __construct(
private TokenGeneratorInterface $tokenGenerator,
private UrlGeneratorInterface $urls,
) {}
public function buildLink(User $user): string
{
return $this->urls->generate(
'app_dashboard',
['autologin' => $this->tokenGenerator->generate($user)],
UrlGeneratorInterface::ABSOLUTE_URL,
);
}
Service ID is jmikola_auto_login.token_generator; it was written in the days of $container->get(), so if you're on Symfony 4.4+ it is likely private — inject it (or add a public alias) rather than calling $this->get().
Batch link generation in a console command. This is the canonical usage: a command loads the audience, builds a link per user, and queues an email. Configure the router's request context first or you'll get http://localhost/... URLs:
// config/packages/test|prod — or set at runtime in the command
// $this->urls->getContext()->setHost('example.com')->setScheme('https');
foreach ($this->users->findNewsletterSubscribers() as $user) {
$mailer->send($this->buildLink($user), $user);
}
Always land on a "consume and redirect" route. Put the token on a dedicated route, then redirect to a clean URL so the token drops out of the address bar, referrer headers, and analytics:
#[Route('/welcome', name: 'app_autologin_landing')]
public function landing(): Response
{
// auto_login has already authenticated the request by this point
return $this->redirectToRoute('app_dashboard');
}
Make that route the only place the token is expected, and set Referrer-Policy: no-referrer (or strict-origin-when-cross-origin) globally.
Scope the token to a purpose. The stock token just proves "this is user X" — it grants a full session. If you use these links for unsubscribe or preference centers, don't rely on the resulting login alone; either (a) build a custom token generator that embeds a purpose/context and check it in the landing controller, or (b) keep the landing controller the sole consumer and never expose authenticated app routes from that request.
Custom token generator. Override jmikola_auto_login.token_generator (alias it to your class) implementing the bundle's TokenGeneratorInterface. Typical reasons: appending a payload (user id + campaign id), versioning the secret, or moving to HMAC-SHA256 with a per-user pepper. Keep the same contract so the listener keeps working.
Defence in depth on top of the login. The listener only authenticates; it says nothing about authorization. Guard the destination with access_control / is_granted() exactly as you would for form login, and keep the auto-login firewall's provider restricted to verified users.
Migrating later. When you move to Symfony 5.3+, port the pattern to:
main:
login_link:
check_route: login_check
signature_properties: [id, password]
with LoginLinkHandlerInterface::createLoginLink(). It is single-use, expiring, and maintained — a strict upgrade.
auto_login: true only resolves if SecurityFactoryInterface exists. On 6.x you get "Unrecognized firewall option" / bundle load failures. Don't adopt this for new work.user_identifier must match the provider's lookup property. If it's email but your entity provider uses username, the listener queries by the wrong field and authentication silently fails (or throws UsernameNotFoundException). Align jmikola_auto_login.user_identifier with security.providers.*.entity.property.lifetime, a generated token is stable for that user; with lifetime, it's a time window. There is no revocation and no replay protection. Treat a leaked link as a leaked password: short lifetimes, HTTPS only, and a password-based signature_properties-style invalidation (rotate via a user field / secret rotation) if you need to revoke.%kernel.secret%. Changing it (or having different secrets per environment) invalidates everything already sent — expected in staging, surprising in prod if you rotate secrets casually.Referer. Always redirect to a clean URL, set a referrer policy, and avoid GET links in contexts where the URL is shared onward.security.yaml or the bundle config, bin/console cache:clear — a stale container is the most common "my token doesn't work" cause.localhost). Set host/scheme/base URL on router.request_context.* or $urlGenerator->getContext() before generating.bin/console debug:firewall main (4.4+) or the web profiler's Security panel (_security_main), plus the security log channel. If the token is rejected, drop a breakpoint/log in vendor/jmikola/auto-login-bundle/Security/AutoLoginListener.php — the class is small and readable, and the failure is almost always identifier mismatch, expired lifetime, or secret mismatch.KernelBrowser are straightforward: generate a token with the test container's generator (same %kernel.secret%), request /$route?autologin=$token, assert the redirect and the authenticated user. Pin the lifetime in test config so tokens don't expire mid-suite.How can I help you explore Laravel packages today?