Symfony 8.2 New Published on

Change a secret without breaking the links already sent

The secret of framework.uri_signer accepts a list: the first one signs new links, all of them check the ones still around.

Topics covered: HttpFoundationSecurity

New in Symfony 8.2 (in development). Signed URLs, login links, "remember me" cookies and signed Messenger messages all rely on a secret. Changing it, after a leak or out of plain hygiene, broke everything it had signed at once: the links already sent by email stopped working.

A list of secrets

Each feature that signs now has its secret option, which accepts a list. The first one signs, all of them check; each one defaults to kernel.secret.

# config/packages/framework.yaml
framework:
    uri_signer:
        secret: ['%env(URI_SIGNER_SECRET)%', '%kernel.secret%']

    messenger:
        serializer:
            signing_secret: ['%env(MESSENGER_SIGNING_SECRET)%', '%kernel.secret%']
# config/packages/security.yaml
security:
    firewalls:
        main:
            remember_me:
                secret: ['%env(REMEMBER_ME_SECRET)%', '%kernel.secret%']

You remove the old secret when nothing it signed is in use anymore: once the links sent with it have expired. Messenger messages are signed again on each retry, and "remember me" cookies on each login: they move to the new secret by themselves.

Symfony 8.2 also adds sign: true on a Messenger transport: all its messages are signed, and a badly signed message is rejected before it is even read.

Your task

On October 24, the team replaced the secret that signs invitations: URI_SIGNER_SECRET holds the new one, URI_SIGNER_OLD_SECRET the old one (see config/packages/secrets.yaml). Since then, every invitation sent before answers 404.

  1. The invitations already sent must work until they expire, and the new ones must be signed with the new secret.
  2. The controller signs without an expiration, which Symfony 8.2 deprecates: give links a default expiration of seven days, in the configuration.

Where the feature comes from: the original pull request.