Symfony security includes several significant improvements and new features
in Symfony 7.3.
Deprecate eraseCredentials()
Method
Contributed by
Robin Chalas
and Nicolas Grekas
in
#59682
The eraseCredentials()
method has been part of the UserInterface
since
its introduction. It is used to delete sensitive data on the user object,
typically their password:
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
// ...
public function eraseCredentials(): void
{
// clear any temporary, sensitive data on the user, e.g.:
// $this->plainPassword = null;
}
}
In Symfony 7.3, this method has been deprecated as it's no longer considered
a best practice. Instead, you should use a dedicated DTO or manually delete
sensitive data during the AuthenticationTokenCreatedEvent
.
To ease the upgrade path, Symfony 7.3 allows you to add the #[\Deprecated]
attribute to your eraseCredentials()
method. When present, Symfony will
stop calling the method automatically.
Support Hashed Passwords in the Session
Contributed by
Nicolas Grekas
in
#59562
Storing any form of a password in the session, plain or hashed, can be risky.
To mitigate this, you can implement the __serialize()
method in your user
class to exclude or transform the password before it's stored in the session.
If you remove the password entirely, getPassword()
returns null
after
unserialization. In that case, Symfony refreshes the user without password
verification, which is only relevant if you're storing plaintext passwords (which
is not recommended).
Symfony 7.3 improves on this by allowing you to store CRC32 hashed passwords
in the session. Symfony will hash the password of the refreshed user and
compare it to the session value. This avoids storing real hashes and allows
you to invalidate sessions when a password changes.
For example, if your password is stored in a private password
property:
class User
{
// ...
public function __serialize(): array
{
$data = (array) $this;
$data["\0".self::class."\0password"] = hash('crc32c', $this->password);
return $data;
}
OAuth2 Token Introspection Endpoint
Contributed by
Florent Morselli
in
#50027
Symfony 7.3 introduces built-in support for the OAuth2 Token Introspection Endpoint
as defined in RFC 7662. This allows Symfony apps to validate access tokens
and fetch related user information by querying the authorization server, removing
the need for your application to decode access tokens on its own.
This simplifies token handling because the OAuth2 specification doesn't mandate
a specific way for resource servers to verify access tokens. These tokens can
have any format and are not always standard JWTs.
This new feature is especially useful when you don't control the format of the
access tokens, which is often the case when you don't own the Authorization Server.
Example configuration:
framework:
http_client:
scoped_clients:
oauth2.client:
base_uri: 'https://authorization-server.example.com/introspection'
scope: 'https://authorization-server\.example\.com'
headers:
# introspection endpoints usually require client authentication
Authorization: 'Basic Y2xpZW50OnBhc3N3b3Jk'
And the corresponding firewall configuration:
# config/security.yaml
security:
# ...
firewalls:
main:
pattern: ^/
access_token:
token_handler:
oauth2: ~
Contributed by
Vincent Chalamon
and Florent Morselli
in
#54932
and #57721
Symfony added OpenID Connect (OIDC) support in version 6.3 as an authentication
mechanism. In Symfony 7.3, we're adding support for OIDC discovery, which
enables clients to fetch server metadata from a well-known URL like
.well-known/openid-configuration
.
This endpoint returns public information such as: endpoints URIs (e.g., userinfo,
token, etc.), public keys for verifying token signatures and other metadata needed
for OIDC client-server interactions. That means less manual configuration and easier
integration with compliant identity providers.
To enable it, configure your firewall as follows:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
oidc:
# no need to define 'keyset' manually anymore
claim: 'email'
audience: 'symfony'
issuers: ['https://example.com/']
algorithms: ['RS256']
discovery:
base_uri: 'https://example.com/oidc/realms/master/'
cache:
id: cache.app # must be created in framework.yaml
OIDC discovery reduces boilerplate and improves maintainability by allowing
dynamic updates from the identity provider.
Contributed by
Christian Gripp
in
#58300
User enumeration is a common security issue where attackers infer valid usernames
based on error messages. For example, a message like "This user does not exist"
shown by your login form reveals whether a username is valid.
Symfony provides the hide_user_not_found option to address this, returning a
generic BadCredentialsException
when the user is not found. However, this
option also hides all other user account status exceptions (e.g. blocked
or expired accounts).
That's why in Symfony 7.3, this option has been renamed to expose_security_errors
and made more flexible by supporting multiple levels:
# config/packages/security.yaml
security:
# ...
# (default value) hide all user-related security exceptions
expose_security_errors: 'none'
# show account-related exceptions (e.g. blocked or expired accounts)
# for users who provided the correct password
expose_security_errors: 'account_status'
# don't hide any security-related exceptions
expose_security_errors: 'all'
Contributed by
Alexandre Daubois
in
#59150
PHP 8.5 (to be released in November 2025) will allow using static callables
inside PHP attributes. Symfony 7.3 anticipates this and updates the
#[IsGranted] attribute to support callables when your PHP version allows it.
You can now write complex access checks in a more expressive and inline manner:
// src/Controller/MyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Security\Http\Attribute\IsGrantedContext;
class MyController extends AbstractController
{
#[IsGranted(static function (IsGrantedContext $context, mixed $subject) {
return $context->user === $subject['post']->getAuthor();
}, subject: static function (array $args) {
return [
'post' => $args['post'],
];
})]
public function index($post): Response
{
// ...
}
}
Special thanks to Robin, Florent, and Christophe for helping review
the contents of this post.
Sponsor the Symfony project.