AXIAM PHP SDK

AxiamClient
in package

FinalYes

The AXIAM PHP SDK's public REST entry point (CONTRACT.md §1–§9, SC#1).

tenant is a REQUIRED, non-nullable-defaulted constructor parameter (D-13, §5) — there is no overload or default that lets a caller omit it; AXIAM is multi-tenant and there is no default tenant. login($email, $password) returns a typed LoginResult, never a raw array/stdClass (D-09); a two-phase MFA flow is completed via self::verifyMfa(). checkAccess/can/batchCheck delegate to AuthzDispatcher — this class never hand-rolls REST/gRPC transport selection (D-03). self::verifyLocallyOrFallback() is the seam the Laravel/Symfony framework bridges (a later plan) call: local JwksVerifier verification first, falling back to the reactive single-flight refresh path (D-02, D-06).

Composition, not reimplementation: this class wires together the already-built wave-2/3 pieces — Session (CookieJar + CSRF + single-flight refresh promise, D-06), AuthMiddleware/RefreshMiddleware (the HandlerStack auth/refresh mechanism), JwksVerifier (local EdDSA/JWKS verification), and AuthzDispatcher (REST-default, gRPC-when-available authz, D-03) — it does not reimplement any of their internal mechanisms.

Two Guzzle clients share ONE CookieJar (§4):

  • $plainHttp carries ONLY AuthMiddleware (tenant/auth/CSRF header injection, no 401-triggered refresh-and-retry). This is the client handed to Session's own constructor for its internal /api/v1/auth/refresh POST (per Session's own doc comment: the refresh call itself must never be able to recursively re-enter the single-flight guard), and is also used for login()/verifyMfa()/logout() — a failed login/logout attempt (401/403) must surface as its own clear error, not trigger an unrelated token-refresh attempt first.
  • $authzHttp carries BOTH AuthMiddleware and RefreshMiddleware — the full production stack — and is the client AuthzRestClient (and therefore AuthzDispatcher's REST path) sends every authz request through, so a 401 on an authz call transparently triggers the shared single-flight refresh-and-retry-once (D-06).

§6/D-12: the Guzzle verify option is ALWAYS true (strict TLS, system trust roots) unless $customCa (a CA bundle FILE PATH) is supplied, in which case verify is set to that path — the ONLY escape hatch. There is no code path in this class that sets verify to false or any other TLS-bypass value.

§6.1 (mTLS): supplying $clientCert + $clientKey (both PEM strings) makes this client present an X.509 client identity for mutual TLS on BOTH transports — the REST Guzzle clients (via cert/ssl_key) and any gRPC channel (via \Grpc\ChannelCredentials::createSsl(rootCerts, privateKey, certChain)). This is strictly ADDITIVE to §6: presenting a client certificate NEVER relaxes server verification — verify is untouched by this code path (contract rule §6.1.2). The private key is secret (§7): it is held behind Sensitive, written only to a 0600 temp file consumed by cURL, and never appears in any debug/log/exception output. Both PEM strings must be supplied together; supplying exactly one is a construction-time InvalidArgumentException.

Table of Contents

Methods

__construct()  : mixed
__destruct()  : mixed
§6.1: cleans up the `0600` temp files backing the client-certificate identity when this client is destroyed, so no PEM material (least of all the private key) outlives the object on disk. A no-op when mTLS was not configured.
batchCheck()  : array<int, bool>
`batchCheck` — results preserve input order (CONTRACT.md §1).
can()  : bool
`can` — the browser/UI-scenario alias for {@see self::checkAccess()} (CONTRACT.md §1 note). Argument order is `(action, resource)` — matching {@see self::checkAccess()} and every other AXIAM SDK's `can`/`Can` (D-09/SDK-Q09; this was previously reversed relative to the rest of the SDK family).
checkAccess()  : bool
`checkAccess` — delegates to {@see AuthzDispatcher} (REST default, gRPC when available).
debugClientCertOptions()  : array{cert: string|null, ssl_key: string|null}
Test-only seam (not part of the public API contract, mirroring {@see self::debugVerifyOption()}): exposes the §6.1 client-identity options (`cert` = certificate-chain file, `ssl_key` = private-key file) actually configured on this client's authz Guzzle transport, so tests can assert the mTLS wiring without performing a live TLS handshake. Both entries are `null` when mTLS was not configured. The values are FILE PATHS, never the PEM bytes — this seam never surfaces the private key itself.
debugVerifyOption()  : string|bool
getUserInfo()  : UserInfo
`getUserInfo` — the gRPC-ONLY OIDC-style userinfo operation (CONTRACT.md §1.1, contract 1.3): returns the authenticated caller's identity claims from `axiam.v1.UserInfoService/GetUserInfo`, the low-latency counterpart of the server's REST `GET /oauth2/userinfo`. Delegates to {@see AuthzDispatcher} — this class never hand-rolls the gRPC transport (D-03).
login()  : LoginResult
`POST /api/v1/auth/login` (CONTRACT.md §1). Returns a typed {@see LoginResult} — an MFA challenge (HTTP 202) is an expected outcome, not an exception: callers MUST check {@see LoginResult::$mfaRequired} before assuming a session was established (SC#1).
logout()  : void
`POST /api/v1/auth/logout` (CONTRACT.md §1) and clears local session state: the shared cookie jar (§4) and the captured CSRF token (§3). The session id comes from the current access token's `jti` claim (unverified decode — an operational hint only, never an authorization decision, mirroring the Python/C# sibling SDKs).
refresh()  : void
`POST /api/v1/auth/refresh` (CONTRACT.md §1), routed through {@see Session}'s single-flight guard (§9, D-06) — the SAME mechanism {@see RefreshMiddleware} triggers reactively on a `401`. A failure surfaces as {@see AuthError} with no retry (§9.3).
verifyLocallyOrFallback()  : array<string, mixed>|null
Local-first JWT verification with a reactive-refresh fallback (D-02) — the seam the Laravel/Symfony framework bridges (a later plan) call for every incoming request.
verifyMfa()  : LoginResult
`POST /api/v1/auth/mfa/verify` (CONTRACT.md §1) — completes the two-phase flow started by {@see self::login()} when {@see LoginResult::$mfaRequired} was `true`. `$challengeToken` is the `Sensitive`-wrapped value from that `LoginResult` (D-11: never a raw string on the public surface).

Methods

__construct()

public __construct(string $baseUrl, string $tenant[, string|null $orgSlug = null ][, string|null $orgId = null ][, string|null $customCa = null ][, string|null $clientCert = null ][, string|null $clientKey = null ][, LoggerInterface|null $logger = null ][, bool|null $restOnly = null ][, int $cacheTtlSeconds = 300 ][, string|null $grpcTarget = null ][, callable|null $transportHandler = null ]) : mixed
Parameters
$baseUrl : string

The AXIAM server's base URL (e.g. https://api.axiam.example).

$tenant : string

The tenant slug — REQUIRED, no nullable default anywhere on this signature (D-13, §5). There is no default tenant; constructing this client without one is a compile-time (missing required argument) error, and an empty string is rejected at runtime as a backstop.

$orgSlug : string|null = null

Organization slug — mutually exclusive with $orgId. The real login/refresh handlers require an org identifier beyond CONTRACT.md §5's tenant-only minimum (mirrors the Python/C# sibling SDKs' org_slug/org_id constructor options).

$orgId : string|null = null

Organization UUID — mutually exclusive with $orgSlug.

$customCa : string|null = null

A CA bundle FILE PATH (PEM-encoded) — the ONLY TLS escape hatch (§6/D-12). Never pass a value here to disable TLS verification; there is no such option on this class.

$clientCert : string|null = null

§6.1 (mTLS): the client's X.509 identity certificate CHAIN as a PEM STRING (not a path). When supplied together with $clientKey, this client presents that certificate for mutual TLS on both the REST and gRPC transports. Purely additive — server verification is never relaxed (§6.1.2). Must be a PEM value; a non-PEM string is rejected at construction. null (default) leaves the default bearer-cookie behavior unchanged (§6.1.5).

$clientKey : string|null = null

§6.1/§7 (mTLS): the PEM STRING of the private key matching $clientCert (PKCS#8 or PKCS#1). Secret material — it is held behind Sensitive and never logged, displayed, or exposed via a getter. $clientCert and $clientKey are all-or-nothing: supplying exactly one throws InvalidArgumentException.

$logger : LoggerInterface|null = null

Injectable logger (D-15: diagnostic-only — status codes and operation names, NEVER a token/credential value). Defaults to a silent NullLogger.

$restOnly : bool|null = null

Force REST-only authz transport. null (default) resolves to true when $grpcTarget is not supplied (there would be nothing to connect the gRPC transport to) and false otherwise — an explicit true/false always wins. REST authz ALWAYS works regardless of this setting (D-03).

$cacheTtlSeconds : int = 300

JwksVerifier's local JWKS TTL cache lifetime.

$grpcTarget : string|null = null

gRPC target host:port (e.g. api.axiam.example:9443), required only to actually use the gRPC authz transport.

$transportHandler : callable|null = null

Test-only seam (NOT part of the public API contract, trailing/optional so it never affects SC#1's "tenant is required" reflection check): a raw Guzzle handler (e.g. GuzzleHttp\Handler\MockHandler) used as the base handler for both internal HandlerStacks instead of Guzzle's default cURL/stream handler. Mirrors the C# sibling SDK's CreateForTesting internal seam, adapted to Guzzle's own documented HandlerStack::create($mockHandler) testing idiom (docs.guzzlephp.org/en/stable/testing.html) — never used by production code.

__destruct()

§6.1: cleans up the `0600` temp files backing the client-certificate identity when this client is destroyed, so no PEM material (least of all the private key) outlives the object on disk. A no-op when mTLS was not configured.

public __destruct() : mixed

batchCheck()

`batchCheck` — results preserve input order (CONTRACT.md §1).

public batchCheck(array<int, array{action: string, resourceId: string, scope?: string|null}> $checks) : array<int, bool>
Parameters
$checks : array<int, array{action: string, resourceId: string, scope?: string|null}>
Return values
array<int, bool>

can()

`can` — the browser/UI-scenario alias for {@see self::checkAccess()} (CONTRACT.md §1 note). Argument order is `(action, resource)` — matching {@see self::checkAccess()} and every other AXIAM SDK's `can`/`Can` (D-09/SDK-Q09; this was previously reversed relative to the rest of the SDK family).

public can(string $action, string $resource) : bool
Parameters
$action : string
$resource : string
Return values
bool

checkAccess()

`checkAccess` — delegates to {@see AuthzDispatcher} (REST default, gRPC when available).

public checkAccess(string $action, string $resourceId[, string|null $scope = null ][, string|null $subjectId = null ]) : bool
Parameters
$action : string
$resourceId : string
$scope : string|null = null
$subjectId : string|null = null

Additive, optional (CONTRACT.md §11.2.2 — declarative authorization helpers): when given, the check is evaluated for THIS subject (a UUID) rather than whichever identity this client's own session represents. This matters for a framework bridge sharing ONE AxiamClient instance (typically authenticated as a service account, or not authenticated at all) to authorize each inbound HTTP request's OWN end user: passing subjectId: $endUserId here checks the end user's permissions, never the shared client's. null (the default) preserves this method's pre-§11 behavior exactly.

Return values
bool

debugClientCertOptions()

Test-only seam (not part of the public API contract, mirroring {@see self::debugVerifyOption()}): exposes the §6.1 client-identity options (`cert` = certificate-chain file, `ssl_key` = private-key file) actually configured on this client's authz Guzzle transport, so tests can assert the mTLS wiring without performing a live TLS handshake. Both entries are `null` when mTLS was not configured. The values are FILE PATHS, never the PEM bytes — this seam never surfaces the private key itself.

public debugClientCertOptions() : array{cert: string|null, ssl_key: string|null}
Return values
array{cert: string|null, ssl_key: string|null}

debugVerifyOption()

public debugVerifyOption() : string|bool
Return values
string|bool

The Guzzle verify option: true, or a CA bundle path (never false).

getUserInfo()

`getUserInfo` — the gRPC-ONLY OIDC-style userinfo operation (CONTRACT.md §1.1, contract 1.3): returns the authenticated caller's identity claims from `axiam.v1.UserInfoService/GetUserInfo`, the low-latency counterpart of the server's REST `GET /oauth2/userinfo`. Delegates to {@see AuthzDispatcher} — this class never hand-rolls the gRPC transport (D-03).

public getUserInfo() : UserInfo

Identity is derived server-side from the current bearer token; the request is empty. sub/tenantId/orgId are always populated on the returned UserInfo; email is present only with the "email" token scope and preferredUsername only with "profile" (the server gates them exactly as the REST endpoint does). Requires a prior successful self::login() — calling it with no token raises AuthError before any wire call (§1.1.3) — and, being gRPC-only (§1.1.6), requires the grpc PECL extension plus a configured grpcTarget; there is NO REST fallback, so on a REST-only runtime it raises NetworkError rather than degrading. A gRPC UNAUTHENTICATED response drives the shared single-flight refresh (§9) and retries once (§1.1.4).

Return values
UserInfo

login()

`POST /api/v1/auth/login` (CONTRACT.md §1). Returns a typed {@see LoginResult} — an MFA challenge (HTTP 202) is an expected outcome, not an exception: callers MUST check {@see LoginResult::$mfaRequired} before assuming a session was established (SC#1).

public login(string $email, string $password) : LoginResult
Parameters
$email : string
$password : string
Return values
LoginResult

logout()

`POST /api/v1/auth/logout` (CONTRACT.md §1) and clears local session state: the shared cookie jar (§4) and the captured CSRF token (§3). The session id comes from the current access token's `jti` claim (unverified decode — an operational hint only, never an authorization decision, mirroring the Python/C# sibling SDKs).

public logout() : void

refresh()

`POST /api/v1/auth/refresh` (CONTRACT.md §1), routed through {@see Session}'s single-flight guard (§9, D-06) — the SAME mechanism {@see RefreshMiddleware} triggers reactively on a `401`. A failure surfaces as {@see AuthError} with no retry (§9.3).

public refresh() : void

verifyLocallyOrFallback()

Local-first JWT verification with a reactive-refresh fallback (D-02) — the seam the Laravel/Symfony framework bridges (a later plan) call for every incoming request.

public verifyLocallyOrFallback(string $token, string $tenant) : array<string, mixed>|null

Tries JwksVerifier::verify() first (no network call on the happy path); if that fails (expired/unknown-kid/invalid token), attempts the shared single-flight refresh (§9, D-06) and re-verifies the FRESH access token. Returns null — never unverified claims — on any failure; this method is fail-closed exactly like JwksVerifier itself, since callers use the result for an authorization decision.

Parameters
$token : string
$tenant : string
Return values
array<string, mixed>|null

Verified claims, or null.

verifyMfa()

`POST /api/v1/auth/mfa/verify` (CONTRACT.md §1) — completes the two-phase flow started by {@see self::login()} when {@see LoginResult::$mfaRequired} was `true`. `$challengeToken` is the `Sensitive`-wrapped value from that `LoginResult` (D-11: never a raw string on the public surface).

public verifyMfa(Sensitive $challengeToken, string $totpCode) : LoginResult
Parameters
$challengeToken : Sensitive
$totpCode : string
Return values
LoginResult
On this page

Search results