api-platform / api-platform/core
[Security] Support voter reasons in the "access denied" responses
- Dominant language
- PHP
- Stars
- 2.6k
- Forks
- 980
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 48
Description
**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.
Contributor guide
Assessment
This issue has not been assessed yet.