AXIAM PHP SDK

AccessEnforcer
in package

FinalYes

The single CONTRACT.md §11 ("Declarative Authorization Helpers") enforcement implementation, shared by BOTH framework bridges ({@see \Axiam\Sdk\Symfony\AxiamAccessAttributeListener} and {@see \Axiam\Sdk\Laravel\AxiamAccessMiddleware}) so the `#[RequireAuth]` / `#[RequireAccess]` / `#[RequireRole]` semantics can never drift between the two integrations. Neither bridge re-implements resource resolution, subject propagation, or the error-mapping table below — both call into this class exclusively.

Composition with the §10 guard (CONTRACT.md §11.2.1): this class NEVER extracts or verifies a token itself. It only ever reads the identity already injected by the CONTRACT.md §10 guard (the axiam_user request attribute populated by AxiamMiddleware / AxiamAuthSubscriber), passed in by the caller as $identity. A null identity is always a 401.

Subject propagation (CONTRACT.md §11.2.2): the authorization check is made for the REQUEST's authenticated end user, never for the shared AxiamClient's own session (which, in a framework bridge, is typically a service account, or not authenticated at all). self::enforceAccess() always passes subjectId: $identity['user_id'] to AxiamClient::checkAccess() — the additive $subjectId parameter that method gained specifically so this class can do so without checking the wrong identity's permissions.

Resource resolution (CONTRACT.md §11.2.3), in order of precedence:

  1. RequireAccess::$resourceId — a static UUID literal, when set;
  2. RequireAccess::$resourceParam — a route/path parameter name, looked up in the $routeParams map the caller supplies;
  3. the $resolver callback the caller supplies, consulted when the configured route parameter is absent from $routeParams (or when resourceParam itself is null, signaling "resolve some other way"). A missing or non-UUID-shaped resource value at every step is a 400 invalid_request — never a silent allow, never an empty/nil-UUID fallback.

Error mapping (CONTRACT.md §11.2.5), identical JSON body shape { "error": ..., "message": ... } on every failure path:

  • no identity -> 401 authentication_failed
  • unresolvable resource id -> 400 invalid_request
  • checkAccess denies, or the server 403s -> 403 authorization_denied
  • the calling client's OWN session 401s mid-check, or a transport-level NetworkError occurs -> 503 authz_unavailable (fail closed: deny, never allow, on any transport/availability failure — an AuthError here reflects a problem with the shared client's own credentials, not a verdict about the end user, so it is treated the same as "the authz service could not be reached" rather than manufacturing a false "authenticate again" prompt for a request whose OWN identity was already verified by the §10 guard)

No decision caching (CONTRACT.md §11.2.6): every self::enforceAccess() call performs a fresh checkAccess round-trip; nothing here memoizes an allow/deny outcome across requests.

Redaction (CONTRACT.md §11.2.8): no method on this class ever logs or echoes a token or credential value. A denied/errored check is logged at debug level only, carrying just the action and resolved resourceId — never the identity's raw claims or any bearer token.

Table of Contents

Methods

__construct()  : mixed
enforceAccess()  : JsonResponse|null
`require_access` (CONTRACT.md §11.1): the endpoint requires the authenticated caller to pass an AXIAM authorization check for `$attribute->action` on a resource resolved from the request. This is the ONE codepath both framework bridges call for `#[RequireAccess]` enforcement — see this class's own docblock for the full resource-resolution and error-mapping tables.
enforceAuth()  : JsonResponse|null
`require_auth` (CONTRACT.md §11.1): the endpoint requires an authenticated AXIAM identity — nothing more. Pure sugar over the CONTRACT.md §10 guard for a route where that guard is applied per-endpoint rather than globally.
enforceRole()  : JsonResponse|null
`require_role` (CONTRACT.md §11.1, MAY-level): a LOCAL check (no server round-trip, CONTRACT.md §11.2.9) that the identity's already-verified `roles` intersect with at least one of {@see RequireRole::$roles}. Requires an authenticated identity first (a role check needs somewhere to read roles from), so an unauthenticated caller still gets 401, not 403.

Methods

__construct()

public __construct(AxiamClient $client[, LoggerInterface|null $logger = null ]) : mixed
Parameters
$client : AxiamClient

The shared client used for the actual checkAccess round-trip (REST by default, gRPC when the client is so configured — CONTRACT.md §11.2.7: no new transport code, the existing checkAccess surface is reused as-is).

$logger : LoggerInterface|null = null

Injectable logger (diagnostic-only: action

  • resolved resourceId on a deny/error, NEVER a token/credential value — CONTRACT.md §11.2.8). Defaults to a silent NullLogger.

enforceAccess()

`require_access` (CONTRACT.md §11.1): the endpoint requires the authenticated caller to pass an AXIAM authorization check for `$attribute->action` on a resource resolved from the request. This is the ONE codepath both framework bridges call for `#[RequireAccess]` enforcement — see this class's own docblock for the full resource-resolution and error-mapping tables.

public enforceAccess(array{user_id: string, tenant_id: string, roles: list}|null $identity, RequireAccess $attribute[, array<string, mixed> $routeParams = [] ][, callable(): Array|null $resolver = null ]) : JsonResponse|null
Parameters
$identity : array{user_id: string, tenant_id: string, roles: list}|null

The identity the CONTRACT.md §10 guard already injected, or null.

$attribute : RequireAccess

The #[RequireAccess(...)] attribute instance.

$routeParams : array<string, mixed> = []

The inbound request's route/path parameters (e.g. Symfony's $request->attributes->get('_route_params') or Laravel's $route->parameters()), consulted when RequireAccess::$resourceParam is set.

$resolver : callable(): Array|null = null

A language-idiomatic resolver callback (CONTRACT.md §11.2.3c) for resource identifiers that live somewhere other than a route parameter (a body field, a header, a composite lookup) — consulted only when the configured RequireAccess::$resourceParam is null or absent from $routeParams.

Return values
JsonResponse|null

null when the check is allowed (caller proceeds); a 401/400/403/503 JsonResponse otherwise (see this class's docblock for the exact mapping).

enforceAuth()

`require_auth` (CONTRACT.md §11.1): the endpoint requires an authenticated AXIAM identity — nothing more. Pure sugar over the CONTRACT.md §10 guard for a route where that guard is applied per-endpoint rather than globally.

public enforceAuth(array{user_id: string, tenant_id: string, roles: list}|null $identity) : JsonResponse|null
Parameters
$identity : array{user_id: string, tenant_id: string, roles: list}|null

The identity the CONTRACT.md §10 guard already injected (the axiam_user request attribute), or null when no such identity is present (unauthenticated, or the §10 guard is not installed on this route).

Return values
JsonResponse|null

null when authenticated (caller proceeds); a 401 authentication_failed JsonResponse otherwise.

enforceRole()

`require_role` (CONTRACT.md §11.1, MAY-level): a LOCAL check (no server round-trip, CONTRACT.md §11.2.9) that the identity's already-verified `roles` intersect with at least one of {@see RequireRole::$roles}. Requires an authenticated identity first (a role check needs somewhere to read roles from), so an unauthenticated caller still gets 401, not 403.

public enforceRole(array{user_id: string, tenant_id: string, roles: list}|null $identity, RequireRole $attribute) : JsonResponse|null

Role names are tenant-defined; this check is intentionally coarse and is NOT a substitute for self::enforceAccess(), which is the authoritative, server-verified check (CONTRACT.md §11.2.9).

Parameters
$identity : array{user_id: string, tenant_id: string, roles: list}|null

The identity the CONTRACT.md §10 guard already injected, or null.

$attribute : RequireRole

The #[RequireRole(...)] attribute instance carrying the acceptable role list.

Return values
JsonResponse|null

null when the identity holds at least one of the required roles (caller proceeds); a 401 authentication_failed or 403 authorization_denied JsonResponse otherwise.

On this page

Search results