core icon indicating copy to clipboard operation
core copied to clipboard

[Security] Support voter reasons in the "access denied" responses

Open j-schumann opened this issue 5 months ago • 0 comments

Description
Symfony (since 7.3) supports Voters setting a "reason" for an access decision: https://github.com/symfony/symfony/pull/59771 Please implement an option to output the reason(s) given by the Voter(s) when access to an endpoint is denied.

Example
ApiResource on the User entity with security property:

  new Delete(
      security: "is_granted('DELETE', object)",
  ),

UserVoter:

  case self::DELETE:
      if (null !== $subject->getAccount()) {
          $vote?->addReason('User has an associated account and cannot be deleted');
          return false;
      }

Possible problem response:

{
    detail: "User has an associated account and cannot be deleted",
    status: 403,
    title: "An error occurred",
    type: "/errors/403",
}

Or instead of modifying the detail, return a separate field that may contain a list of reasons, not just the last reason.

Details APIP currently cannot output this reason when it throws an AccessDenied[Http]Exception in the AccessCheckerProvider because its ResourceAccessChecker uses the ExpressionLanguage, which currently has no support for specifying an AccessDecision object like Symfony\Component\Security\Core\Authorization\AuthorizationChecker::isGranted has, that could later be evaluated for the reasons.

APIP's ResourceAccessChecker has its own $authorizationChecker, which could be accessed after evaluating the expression. This $authorizationChecker has an $accessDecisionStack, which is sadly empty and private. But it also has a (private) $accessDecisionManager, which again has an $accessDecisionStack. And this stack has the AccessDecision object with all votes and reasons, but I have no idea how APIP could access this. I hope someone has a better idea than overwriting all those classes/services with custom versions to allow access to those fields.

j-schumann avatar Aug 06 '25 08:08 j-schumann