struct

KemalIdentity::Authz::Forbidden

Inherits Struct < Value < Object

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

insufficient_assurance(permission : String, tenant_id : String | Nil = nil, minimum_assurance : AssuranceLevel | Nil = nil) : self

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.

Source
not_a_member(permission : String, tenant_id : String | Nil = nil) : self

The check named a tenant this principal does not belong to.

Source
not_permitted(permission : String, tenant_id : String | Nil = nil) : self

Nobody granted it.

Source
out_of_scope(permission : String, tenant_id : String | Nil = nil) : self

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.

Source
policy(permission : String, code : String, step_up : Bool = false, tenant_id : String | Nil = nil) : self

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.

Source
tenant_mismatch(permission : String, tenant_id : String | Nil = nil) : self

The principal is bound to one tenant and the check named another.

Source
unknown_permission(permission : String, tenant_id : String | Nil = nil) : self

The permission was never declared.

Source

Instance methods

code

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.

Source
minimum_assurance

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.

Source
permission
Source
permitted?
Source
reason
Source
step_up?

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.

Source
tenant_id
Source