KemalIdentity::Authz::Forbidden
The check said no.
Built with a named constructor, never with new
#step_up? decides control flow — env.auth.authorize! raises
FreshAuthenticationRequiredError when it is true — and if it were an ordinary constructor
argument then RBAC could build an InsufficientAssurance denial and forget to pass it.
Step-up would stop working, silently, and no type would catch it. Before this struct carried
the flag the enum member was the signal, so that mistake was unrepresentable; splitting
the two axes must not buy extensibility with that guarantee.
So initialize is private and each built-in reason has a constructor that fixes the flag.
The one place a caller chooses is .policy, which is the one place the answer is not
already known here. Same shape as Verdict.allow and Verdict.deny.
Constructors
A role grants this and the caller holds it, but has not proved who they are strongly enough. The one built-in reason that step-up can fix.
The check named a tenant this principal does not belong to.
The account holds the permission; the credential presenting the request does not.
step_up: false, and worth stating rather than assuming: re-authenticating does not widen
a token's scope. The attenuation was fixed when the token was issued, and no amount of
proving who you are changes what the credential may carry. Issuing a new token can help,
and that is a remediation rather than a step-up.
An application authorizer's own denial.
code names the reason for the audit trail. step_up is chosen here because this is the
only case where this shard cannot know the answer — but choose it honestly: true only when
authenticating again, or harder, could actually change the outcome.
The principal is bound to one tenant and the check named another.
Instance methods
An application authorizer's own reason, for the audit trail. nil for the built-in ones,
which #reason already names.
Never rendered to a client. Laravel propagates a gate's message into the HTTP response
and Django REST Framework renders one; this shard does not, for the reason DenialReason
gives above — a denial that explains itself confirms which tenants exist and who is in
them. Every denial still renders one identical 403.
The level that would have satisfied the permission, when InsufficientAssurance is why
this was refused.
RBAC compares Permission#minimum_assurance and had the number in hand at the moment
it refused; dropping it left env.auth.authorize! raising a step-up with nothing to say
about what the step is. nil for every other reason, including an application
authorizer's own step_up: true denial — that authorizer knows its own policy and this
shard does not.
Whether stronger or fresher authentication may allow this request to succeed.
Not "the caller is missing something" — "authenticating again, or harder, may change this answer". Joining a tenant, enrolling a managed device or being issued a wider token do not qualify: those are remediations, and no amount of re-authentication performs them. Prompting for a second factor in those cases asks somebody for something that cannot help.