Symfony security includes several significant improvements and new features in Symfony 7.3.

Deprecate eraseCredentials() Method

Robin Chalas Nicolas Grekas
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:

1
2
3
4
5
6
7
8
9
10
11
12
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

Nicolas Grekas
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:

1
2
3
4
5
6
7
8
9
10
11
class User
{
    // ...

    public function __serialize(): array
    {
        $data = (array) $this;
        $data["\0".self::class."\0password"] = hash('crc32c', $this->password);

        return $data;
    }

OAuth2 Token Introspection Endpoint

Florent Morselli
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
# config/security.yaml
security:
    # ...
    firewalls:
        main:
            pattern: ^/
            access_token:
                token_handler:
                    oauth2: ~

OIDC Discovery

Vincent Chalamon Florent Morselli
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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.

Exposing Security Errors

Christian Gripp
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 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'

IsGranted Callables

Alexandre Daubois
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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.

Published in #Living on the edge