Symfony 8.2 Nouveauté Publié le

Un lien de réinitialisation qui ne sert qu'une fois

UriSigner::sign() accepte une « version » : signez le lien avec l'empreinte du mot de passe, et il meurt dès que le mot de passe change.

Notions abordées : HttpFoundationSécurité

Nouveau dans Symfony 8.2 (en développement). UriSigner signe une URL : quiconque la reçoit peut s'en servir, personne ne peut la retoucher. Mais une signature ne dit pas quand le lien doit cesser de servir. Un lien de réinitialisation de mot de passe restait valable après usage, tant qu'il n'avait pas expiré — et sans expiration, pour toujours. Qui retrouvait le mail pouvait changer le mot de passe une seconde fois.

La version

Symfony 8.2 ajoute un argument à sign(), check(), checkRequest() et verify() : une version. Elle entre dans le calcul de la signature, mais pas dans l'URL. Tant qu'elle ne change pas, le lien est valide ; dès qu'elle change, il ne l'est plus.

// à l'envoi du mail
$lien = $signataire->sign($url, new \DateInterval('PT30M'), $utilisateur->getPassword());

// à l'arrivée sur le lien
$signataire->verify($request, $utilisateur->getPassword());

Pour une réinitialisation, la version naturelle est l'empreinte du mot de passe : sitôt le nouveau mot de passe enregistré, l'empreinte change, et tous les liens envoyés jusque-là meurent avec elle. Pour un lien de connexion, ce pourrait être la date de dernière connexion.

verify() lance une UnverifiedSignedUriException (404) quand la signature ne correspond pas, et une ExpiredSignedUriException (403) quand le lien a expiré ; un lien sans signature donne une UnsignedUriException (404).

L'expiration devient obligatoire

Un lien signé sans expiration vaut pour toujours. Symfony 8.2 déprécie sign() sans expiration, et 9.0 l'exigera. On la passe à chaque appel, ou une fois pour toutes :

# config/packages/framework.yaml
framework:
    uri_signer:
        expiration: 3600

À faire

MotDePasseController envoie un lien de réinitialisation (la réponse le montre, à la place du mail) puis l'accepte autant de fois qu'on veut. La requête « Demander un lien » de l'onglet « Requêtes » en produit un.

  1. Signez le lien avec l'empreinte du mot de passe comme version, et vérifiez-le avec l'empreinte actuelle : un lien qui a servi, ou envoyé avant le dernier changement, doit répondre 404.
  2. Faites-le expirer trente minutes après son envoi.

D'où vient la fonctionnalité : la pull request d'origine.