in Symfony, the complete guide
A convenient remember me feature and secure session handling are not mutually exclusive, once you know the right building blocks. This guide shows persistent remember me tokens instead of insecure signed cookies, protection against session fixation, and a timeout strategy that matches the actual sensitivity of the application.
Table of Contents
- 1. Why remember me and session security belong together
- 2. The problem with signed cookie tokens
- 3. Persistent tokens: the secure remember me pattern
- 4. Setting up persistent remember me in Symfony
- 5. Preventing session fixation
- 6. Timeout strategies: idle, absolute and remember me duration
- 7. A logout that really ends everything
- 8. Device overview and targeted revocation
- 9. Remember me strategies compared
- 10. Summary
- 11. FAQ
1. Why remember me and session security belong together
A remember me feature extends a user's login beyond the end of the browser session, often for weeks or months. Exactly this longevity makes it one of the most sensitive building blocks of an application's overall session security: a stolen remember me cookie is potentially usable for much longer than a normal session cookie, which expires after a few hours of inactivity.
Anyone who implements remember me without regard to the underlying session architecture often unknowingly carries over the same weaknesses as with normal sessions, only over a much larger time window during which an attack stays effective. This guide therefore treats both topics together: secure remember me tokens and robust session security as two sides of the same coin, not as separate features.
2. The problem with signed cookie tokens
Symfony's simplest remember me mode, the signed cookie token, encodes the username, expiration time and a signature directly into a cookie, with no database backend at all. That is simple to set up, but has a decisive drawback: there is no way to revoke a single issued cookie without rotating the entire application's signing secret, thereby invalidating every remember me cookie of every user at once.
For session security in serious production environments, that is a dealbreaker. If a laptop with a valid signed remember me cookie is stolen, that cookie stays valid until its built in expiration date, there is no way to selectively deactivate just that one cookie while every other user stays logged in. This inability to revoke selectively is the central reason why the persistent token pattern is the better choice in production.
3. Persistent tokens: the secure remember me pattern
Symfony's PersistentTokenBasedRememberMeHandler stores a database record for every issued remember me cookie, containing a user identifier, a series identifier and a hashed, rotating token value. Every time the remember me cookie is used, the token value in the database is replaced with a new one, a pattern called token rotation. This automatically invalidates an already used, old token, even if the associated series identifier is reused.
This rotation has a decisive security benefit: if an old token, already replaced by rotation, is presented again anyway, a strong indicator of theft, the application can immediately revoke the entire series and actively notify the user, instead of simply rejecting the stolen cookie. For real session security in production, this theft detection mechanism is a significant advantage over the plain signed cookie mode.
# config/packages/security.yaml
security:
firewalls:
main:
remember_me:
secret: '%kernel.secret%'
# Persistent tokens — not signed cookies — enable per-device revocation
token_provider:
doctrine: true
lifetime: 2592000 # 30 days
path: /
# Require a fresh login for sensitive actions even with a valid remember-me cookie
always_remember_me: false
remember_me_parameter: _remember_me
4. Setting up persistent remember me in Symfony
The Doctrine based token storage needs a table with the columns series, value, class, username and last_used. Symfony ships a ready made schema for this, created via bin/console doctrine:schema:update or a migration. Important: the value column does not store the plaintext token, it stores a hash, following exactly the same principle as secure API token authentication, so a database leak does not directly make the tokens usable.
After setup, an additional cookie appears in the browser, independent of the actual session cookie. This remember me cookie contains only the series identifier and the current token value, never the password or any other sensitive value in plaintext. On every page load without an active session, a dedicated remember me listener checks this cookie and, on success, automatically establishes a new authenticated session, without the user having to enter their password again.
5. Preventing session fixation
Session fixation is an attack in which an attacker plants an already known session id on a victim, for example via a manipulated link, and waits for the victim to log in with that id. If it succeeds, the attacker knows the same, now authenticated session id after the victim's login. Symfony protects against this by default, automatically generating a completely new session id on every successful login, a behavior that should never be disabled, not even for supposed performance reasons.
For session security, regenerating the session id at login alone is not always enough. In applications with particularly sensitive privilege changes, for example switching from a normal user account to an administrator context, the session id should also be regenerated on every significant privilege change. Symfony's Security::login() and the corresponding authenticator hooks handle this regeneration automatically, a manually built login mechanism outside the security system has to implement it explicitly itself.
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: LoginSuccessEvent::class)]
final class SessionRegenerationListener
{
public function __invoke(LoginSuccessEvent $event): void
{
$session = $event->getRequest()->getSession();
// Symfony already regenerates the session id on login by default —
// this listener is only needed for additional privilege-escalation points
if ($session instanceof SessionInterface) {
$session->migrate(true); // true: destroy the old session data on the server too
}
}
}
6. Timeout strategies: idle, absolute and remember me duration
Three independent time limits together determine an application's actual session security. The idle timeout, configured via gc_maxlifetime in php.ini or in Symfony's session configuration, ends a session after a certain time without activity. An absolute timeout, not built into Symfony but implemented through a custom listener, ends a session after a fixed maximum duration, regardless of ongoing activity, protecting against permanently open sessions in forgotten browser tabs.
The third time limit is the remember me lifetime itself, often considerably longer than both session timeouts, because it is a deliberately chosen convenience feature, not a security mechanism. For sensitive areas of an application, such as payment settings or administration features, a valid remember me cookie alone should not be enough. Leaving always_remember_me at false and instead requiring a fresh password entry for critical actions combines convenience for everyday use with real protection for critical operations.
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
/**
* Absolute session timeout — Symfony has no built-in equivalent,
* gc_maxlifetime only covers the idle timeout.
*/
#[AsEventListener(event: 'kernel.request', priority: 8)]
final class AbsoluteSessionTimeoutListener
{
private const int MAX_SESSION_LIFETIME = 28800; // 8 hours, regardless of activity
public function __invoke(RequestEvent $event): void
{
$session = $event->getRequest()->getSession();
if (!$session->isStarted()) {
return;
}
$createdAt = $session->get('_session_created_at');
if ($createdAt === null) {
$session->set('_session_created_at', time());
return;
}
if (time() - $createdAt > self::MAX_SESSION_LIFETIME) {
$session->invalidate();
}
}
}
7. A logout that really ends everything
An incomplete logout is one of the most common gaps in session security in many applications: the current PHP session ends, but the remember me cookie stays active and automatically establishes a new authenticated session on the next request, practically a logout that never really happens. Symfony's built in logout handler by default deletes both the session and the remember me cookie as well as the associated database record, but only if remember_me is correctly anchored in the firewall's logout configuration block.
An additional, often overlooked case: logout on a shared device, for example a kiosk machine. Here the application should actively offer to delete all of a user's remember me tokens, not just the current series, so that a forgotten remember me cookie from another session does not stay valid unnoticed. This complete cleanup belongs to a serious remember me implementation, not just the standard logout of the current session.
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Repository\PersistentTokenRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\User\UserInterface;
final class LogoutAllDevicesController extends AbstractController
{
#[Route('/account/logout-all', name: 'app_logout_all_devices', methods: ['POST'])]
public function __invoke(PersistentTokenRepository $tokens): JsonResponse
{
/** @var UserInterface $user */
$user = $this->getUser();
// Deletes every remember-me series for this user, not just the current one —
// essential for the "logout on a shared device" scenario
$deletedCount = $tokens->deleteAllForUser($user->getUserIdentifier());
return $this->json(['revoked_sessions' => $deletedCount]);
}
}
8. Device overview and targeted revocation
Because persistent tokens live in a normal database table, it is easy to build a device overview for the user from them, similar to what larger platforms commonly offer: a list of active remember me series with the last usage time, from which each individual series can be deleted specifically. This is a direct practical advantage of the persistent token model over signed cookies, where such an overview is technically impossible.
For session security in security conscious projects, this overview is more than a convenience feature: it gives users the ability to react themselves to a suspected account compromise, without depending on support. A combination of automatic rotation, theft detection through reused old tokens, and a manual device overview covers the three most important lines of defense for remember me in production applications.
-- Device overview query — one row per active remember-me series
-- "series" is the stable per-device identifier, "value" changes on every rotation
SELECT
series,
last_used,
class AS user_class
FROM rememberme_token
WHERE username = :username
ORDER BY last_used DESC;
-- Targeted revocation of exactly one device/series
DELETE FROM rememberme_token
WHERE username = :username AND series = :series;
9. Remember me strategies compared
Choosing the right remember me strategy depends on the sensitivity of the application. The following overview ranks the available options by security level and implementation effort.
| Strategy | Targeted revocation | Theft detection | Recommendation |
|---|---|---|---|
| Signed cookie | Not possible | None | Only for non-critical demos |
| Persistent token without rotation | Possible | Low | Minimum for production |
| Persistent token with rotation | Possible | High | Recommended standard |
| + device overview for users | Self service | High | For security conscious products |
For the vast majority of production Symfony applications, a persistent token with rotation is the right compromise between security and implementation effort. An additional device overview is worthwhile especially for applications with sensitive data, where users should be able to react themselves to a suspected account issue without having to contact support.
Mironsoft
Session security, login architecture and Symfony backend
Remember me that combines convenience and security?
We migrate existing remember me implementations to persistent tokens with rotation, harden session handling against fixation and theft, and build device overviews with targeted revocation.
Session audit
Check existing remember me and session configuration for gaps
Token migration
Move signed cookies to persistent, rotating tokens
Device overview
Implement self service revocation for users
10. Summary
Remember me and session security belong together in consideration, because a long lived remember me cookie stays effective for much longer than a normal session in case of a compromise. Persistent tokens with rotation replace insecure signed cookies, enable targeted revocation, and detect theft through reused old tokens. Session id regeneration on login and on privilege changes reliably prevents session fixation.
Idle timeout, absolute timeout and remember me lifetime are three independent time limits that together determine actual security, critical actions should require a fresh password confirmation despite a valid remember me cookie. A complete logout deletes the session, the remember me cookie and the associated database record together, and a device overview gives users additional control over their active logins.
Remember Me and Session Security — The Key Points at a Glance
Persistent tokens instead of signed cookies
Targeted revocation of individual devices is only possible with database backed tokens, not signed cookies.
Token rotation as theft detection
A reused old token after rotation is a strong indicator of theft and should revoke the entire series.
Prevent session fixation
Session id regeneration on login and on every significant privilege change is mandatory, never disable it.
Three independent time limits
Idle timeout, absolute timeout and remember me lifetime must be configured deliberately and separately.