Symfony 8.2 New Published on

Ask for the right factor again instead of a 403

Vote::requestReAuthentication(): your voter can send the user to a new authentication, and the entry point chooses which one.

Topics covered: Security

New in Symfony 8.2 (in development). With sudo mode, a denied IS_AUTHENTICATED_RECENTLY sends the user, through the firewall's re-authentication entry point, to a new authentication instead of a 403. Symfony 8.2 opens this mechanism to your attributes: a voter can tell the firewall that a new proof would change its answer.

The voter notifies the firewall

protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
    if (/* the expected proof is there */) {
        return true;
    }

    $vote?->requestReAuthentication($attribute);

    return false;
}

The firewall remembers the requested page, notes the denied attribute in the request, then calls its re-authentication entry point, the same one sudo mode uses:

$denied = $request->attributes->get(SecurityRequestAttributes::RE_AUTHENTICATION_ATTRIBUTE);

The entry point can thus ask for the right proof: a code for a transfer, the password for everything else. This split is our choice, not a Symfony rule: IS_AUTHENTICATED_RECENTLY looks at the most recent proof, whatever its method.

Two limits. Only ask for a new proof if it can change the answer: for someone who can never provide it, a 403 is more honest than a loop. And the mechanism only kicks in when a single attribute is denied: a check that combines several always gives a 403.

Your task

The bank from the previous exercise: Ada (ada / lovelace) and Linus (linus / torvalds) receive their codes by SMS, Grace (grace / hopper) gave no phone number. Without a recent code, /transfers answers 403 to all three. /contact-details requires IS_AUTHENTICATED_RECENTLY.

  1. CodeSmsRecentVoter asks for a new authentication when it denies RECENT_SMS_CODE, but only to those who can receive a code. Grace keeps her 403.
  2. DemanderUnePreuve sends to the code form when RECENT_SMS_CODE was the denied attribute, to the login page otherwise.
  3. Declare it as the re-authentication entry point of the main firewall.

Once the code is entered, Ada comes back to her transfer.

Where the feature comes from: the original pull request.