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.
audit()  : AuditApi
Append-only audit log, read-only by construction.
batchCheck()  : array<int, bool>
`batchCheck` — results preserve input order (CONTRACT.md §1).
caCertificates()  : CaCertificatesApi
Organization CAs and the per-tenant signing CAs chained beneath them.
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).
certificates()  : CertificatesApi
End-entity X.509 certificates -- the ones issued to users, services and IoT devices.
checkAccess()  : bool
`checkAccess` — delegates to {@see AuthzDispatcher} (REST default, gRPC when available).
close()  : void
Releases this client's local resources (CONTRACT.md §18).
confirmPasswordReset()  : void
`POST /api/v1/auth/reset/confirm` (CONTRACT.md §25.1) — set the new password.
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
deviceAuthorize()  : DeviceAuthorization
`POST /oauth2/device_authorization` (CONTRACT.md §14.1) — start the device grant and obtain the code pair.
deviceLogin()  : OidcTokenSet
The composed §14.3 helper: start the grant, hand the caller the user code (before the first poll), poll to completion.
devicePoll()  : OidcTokenSet
`POST /oauth2/token` with the device-code grant (CONTRACT.md §14.1) — **one** poll attempt, for an application driving its own loop. Most callers want {@see self::deviceLogin()}.
emailConfig()  : EmailConfigApi
Transactional-mail transport, configurable at organization level and overridable per tenant.
federation()  : FederationApi
Upstream IdP configuration and the per-user links it produces.
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).
groups()  : GroupsApi
Named collections of users. Roles assigned to a group are inherited by every member.
introspect()  : IntrospectionResult
`POST /oauth2/introspect` (RFC 7662, CONTRACT.md §12.1) — ask the server whether a token is active and, if so, for its metadata. Requires confidential-client credentials (§12.1 note 4). A `401` here is a client-credential failure surfaced as {@see \Axiam\Sdk\Core\OAuthProtocolError} and NEVER enters the §9 refresh guard (§12.3 rule 3).
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).
loginClientCredentials()  : OidcTokenSet
`POST /oauth2/token` with `grant_type=client_credentials` (CONTRACT.md §12.1) — service-account machine-to-machine login. Requests no `openid` scope, so the response carries no `id_token`. Pass `$adoptAsCredential: true` to additionally adopt the returned access token as this client's bearer credential for subsequent same-origin REST calls (§12.1, an opt-in MAY) — the token is held behind {@see Sensitive} inside {@see Session} and is NEVER sent to `/oauth2/*`.
loginOpaque()  : LoginResult
`POST /api/v1/auth/opaque/login/start` followed by `/finish` — OPAQUE login, RFC 9807 (CONTRACT.md §23).
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).
logoutUrl()  : string
Build the RP-initiated logout URL to redirect the user agent to (CONTRACT.md §12.7.2). Does **not** clear this client's own session.
management()  : ManagementApi
The CONTRACT.md §27 management surface: 147 operations across 24 namespaces.
mfaConfirm()  : bool
`POST /api/v1/auth/mfa/confirm` (CONTRACT.md §25.1) — activate the factor {@see self::mfaEnroll()} offered. Returns whether MFA is now enabled.
mfaEnroll()  : MfaEnrollment
`POST /api/v1/auth/mfa/enroll` (CONTRACT.md §25.1) — start voluntary TOTP enrolment for the signed-in user.
mfaSetupConfirm()  : LoginResult
`POST /api/v1/auth/mfa/setup/confirm` (CONTRACT.md §25.1) — finish forced enrolment and, with it, the login that was interrupted.
mfaSetupEnroll()  : MfaEnrollment
`POST /api/v1/auth/mfa/setup/enroll` (CONTRACT.md §25.1) — start the enrolment a {@see self::login()} demanded.
notificationRules()  : NotificationRulesApi
Which events raise a notification, and to whom.
oauth2Clients()  : Oauth2ClientsApi
Registered OAuth2/OIDC clients -- the registration half of what §12, §21 and §26 then speak to.
oidcBegin()  : AuthorizationRequest
Build an authorization request (CONTRACT.md §12.1) — **pure local computation, no network I/O**. Generates a `state`/`nonce` (CSPRNG, ≥128 bits) and a fresh PKCE verifier/challenge pair (**S256 only**), and builds `$configuration`'s `authorization_endpoint` into a redirect URL with exactly the eight SDK-owned query parameters plus any `$extraParams` supplied.
oidcDiscover()  : OidcConfiguration
`GET /.well-known/openid-configuration` (CONTRACT.md §12.1) — fetch the OIDC discovery document, cached per origin with a ≥5-minute TTL and single-flight de-duplication of concurrent callers (§12.3 rule 6). The document's own `issuer` is authoritative for ID-token validation (§12.4 rule 3) and may legitimately differ from `$baseUrl` behind a proxy — never treated as an error (§12.3 rule 6).
oidcExchange()  : OidcTokenSet
`POST /oauth2/token` with `grant_type=authorization_code` (CONTRACT.md §12.1) — exchange an authorization code for a token set, validating the returned ID token in full (§12.4) before returning. `$nonce` is MANDATORY: this grant always requests `openid`, so §12.4 rule 6 always applies. On ANY §12.4 failure the whole token set is discarded and {@see AuthError} is raised with the matching reason code (§12.4 rule 7) — `getReason()` returns one of `invalid_alg`, `unknown_kid`, `invalid_signature`, `invalid_issuer`, `invalid_audience`, `token_expired`, `nonce_mismatch`.
oidcPar()  : PushedAuthorizationRequest
`POST /oauth2/par` (CONTRACT.md §26.1) — push the authorization request over the back channel and get an opaque handle to redirect with.
oidcRefresh()  : OidcTokenSet
`POST /oauth2/token` with `grant_type=refresh_token` (CONTRACT.md §12.1) — refresh an {@see OidcTokenSet} under the SAME §9 single-flight guard {@see self::refresh()} uses. A **distinct operation** from {@see self::refresh()} (the cookie/opaque-token session path) — the two are never merged, aliased, or fall back to one another, but they share ONE guard slot: a concurrent `oidcRefresh()` call finding the guard busy with a cookie-session refresh retries (bounded) rather than returning a stale token set.
opaqueAvailable()  : bool
Whether this installation can perform OPAQUE (§23.2).
opaqueEnrollment()  : OpaqueEnrollment
Builds a registration record for `$password`, to send with any request that sets one: `POST /api/v1/users`, `/auth/password/change`, `/auth/reset/confirm` and `/admin/bootstrap`.
opaqueEnrollmentForSelf()  : OpaqueEnrollment
Builds a registration record for the **caller's own** new password, sealed against the tenant the caller's account lives in.
organizations()  : OrganizationsApi
Organizations an SDK client may read and configure. Creation and deletion are outside the SDK boundary (§27.0).
passwordResetContext()  : PasswordResetContext
`GET /api/v1/auth/reset/context` (CONTRACT.md §25.1) — the OPAQUE policy for the account a reset token belongs to.
permissions()  : PermissionsApi
Permissions -- an action on a resource, optionally narrowed by a scope.
pgpKeys()  : PgpKeysApi
OpenPGP keys used for audit signing and encrypted data export.
platform()  : PlatformApi
Deployment-level probes and FIDO metadata state. Unauthenticated where the server leaves them so.
privacy()  : PrivacyApi
GDPR self-service: the authenticated account's own export and erasure. Scoped to the caller, never to another user.
reactors()  : ReactorsApi
Registration of §22 AMQP extension actors -- the admin surface §22.9 describes, which no SDK could previously reach.
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).
requestPasswordReset()  : void
`POST /api/v1/auth/reset` (CONTRACT.md §25.1) — ask for a reset mail.
resendOwnVerification()  : void
`POST /api/v1/users/me/resend-verification` (CONTRACT.md §25.1, §25.7) — resend the **signed-in caller's own** verification mail, and say what happened.
resendVerification()  : void
`POST /api/v1/auth/resend-verification` (CONTRACT.md §25.1) — the **unauthenticated** resend, for a caller with no session.
resolvedOrgId()  : string|null
The organization UUID §27 routes substitute for `{org_id}`, or `null` when the client was constructed without one (§27.4 rule 3).
resolvedTenantId()  : string|null
The tenant UUID §27 routes substitute for `{tenant_id}`, or `null` when the client was constructed without one (§27.4 rule 3).
resources()  : ResourcesApi
The resource hierarchy role assignments cascade down.
revoke()  : void
`POST /oauth2/revoke` (RFC 7009, CONTRACT.md §12.1) — revoke an access or refresh token. Returns nothing. Per RFC 7009 the server answers `200` for an unknown, expired, or already-revoked token too, so this call is **idempotent**: only a `401` (client authentication failed) is an error, surfaced as {@see \Axiam\Sdk\Core\OAuthProtocolError} (§12.1 note 5). A `5xx` still raises {@see NetworkError}.
roles()  : RolesApi
Roles, their permission sets, and their assignment to users and groups.
scimTokens()  : ScimTokensApi
Bearer tokens for the SCIM 2.0 provisioning endpoint.
scopes()  : ScopesApi
Sub-resource granularity, always addressed under their resource.
serviceAccounts()  : ServiceAccountsApi
Machine identities, their secrets, and the certificate a device-bound one authenticates with.
settings()  : SettingsApi
Effective settings, and the organization/tenant layers they resolve from.
ssoComplete()  : SsoCompleteResult
`POST /api/v1/auth/federation/oidc/callback` (CONTRACT.md §12.1) — step 2 of upstream SSO: consumes the single-use `$state`, provisions or links the user, and establishes the session. The session arrives as `Set-Cookie` — not in the response body (§12.1 note 6) — so it is captured automatically via this client's shared §4 cookie jar; the freshly-issued §3 CSRF token is captured too, exactly as {@see self::login()} does. §12.4 does not apply here: no ID token ever reaches the SDK on the federation path.
ssoCompleteHandoff()  : SsoCompleteResult
`POST /api/v1/auth/federation/handoff` (CONTRACT.md §12.1, contract 1.38) — redeem the single-use `axiam_handoff` code the SAML and Apple flows deliver.
ssoCompleteOauth2()  : SsoCompleteResult
`POST /api/v1/auth/federation/oauth2/callback` (CONTRACT.md §12.1, contract 1.38) — step 2 of a plain-OAuth2 login. Same `Set-Cookie` session and same §3 CSRF capture as {@see self::ssoComplete()}. §12.4 does not apply: an `OAuth2` provider issues no ID token at all (§12.1 note 11).
ssoProviders()  : FederationProviderList
`GET /api/v1/auth/federation/providers` (CONTRACT.md §12.1, contract 1.38) — which "Sign in with X" buttons to render for a workspace. The identifiers travel as **query** parameters; this is a `GET` and sends no body.
ssoStart()  : SsoStartResult
`POST /api/v1/auth/federation/oidc/start` (CONTRACT.md §12.1) — step 1 of first-time SSO against an **upstream** IdP. No JWT required. One tenant form (`$tenantId`/`$tenantSlug`) and one org form (`$orgId`/`$orgSlug`) must be resolvable, from the arguments or from this client's construction options (§5.1) — an unresolvable one raises {@see AuthError} client-side, with no wire call. Redirect the browser to the returned `authorizeUrl` and round-trip `state` back into {@see self::ssoComplete()} unmodified; the server keeps the nonce to itself (§12.1 note 7).
ssoStartOauth2()  : SsoStartResult
`POST /api/v1/auth/federation/oauth2/start` (CONTRACT.md §12.1, contract 1.38) — step 1 of a login through a **plain-OAuth2** upstream (GitHub, Facebook, `generic_oauth2`). Call this, rather than {@see self::ssoStart()}, exactly when the provider's `protocol` is `OAuth2` (§12.1 note 10).
tenants()  : TenantsApi
Tenants within an organization -- the isolation boundary every other namespace is scoped to.
tokenExchange()  : ExchangedToken
`POST /oauth2/token` with the RFC 8693 grant (CONTRACT.md §15.1) — exchange a token for a **narrower** one.
umaChallengeHeader()  : string
Format a `WWW-Authenticate: UMA` header (CONTRACT.md §20.3, emit half).
umaDeleteResource()  : void
`DELETE /uma2/rreg/resource_set/{id}` (CONTRACT.md §20.1).
umaExchangeTicket()  : RequestingPartyToken
The UMA ticket grant (CONTRACT.md §20.1) — redeem a permission ticket for an RPT.
umaListResources()  : array<int, string>
`GET /uma2/rreg/resource_set` (CONTRACT.md §20.1) — the ids this client registered.
umaParseChallenge()  : UmaChallenge|null
Parse a `WWW-Authenticate: UMA …` header (CONTRACT.md §20.3) — local computation, and deliberately **no** exchange of the ticket it finds.
umaReadResource()  : ResourceSet
`GET /uma2/rreg/resource_set/{id}` (CONTRACT.md §20.1).
umaRegisterResource()  : ResourceSet
`POST /uma2/rreg/resource_set` (CONTRACT.md §20.1) — register a UMA resource set.
umaRequestTicket()  : Sensitive
`POST /uma2/perm` (CONTRACT.md §20.1) — mint a permission ticket.
umaUpdateResource()  : ResourceSet
`PUT /uma2/rreg/resource_set/{id}` (CONTRACT.md §20.1) — replace a resource set.
users()  : UsersApi
Users within the client's tenant, and the administrative side of their second factor and lockout state.
verifyEmail()  : void
`POST /api/v1/auth/verify-email` (CONTRACT.md §25.1).
verifyLocally()  : array<string, mixed>|null
Verify an INBOUND caller's token and nothing else — the seam every request guard must use (CONTRACT.md §10.1 rule 8). Delegates straight to {@see JwksVerifier::verify()}, which applies the full §10.1 minimum local-verification set, and returns `null` on any failure with **no fallback to another credential**.
verifyLocallyOrFallback()  : array<string, mixed>|null
Local-first JWT verification with a reactive-refresh fallback (D-02) — for the SDK's own OUTBOUND calls, where the token being verified is this client's own and refreshing it is the intended recovery. Tries {@see 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.
verifyLogoutToken()  : VerifiedLogoutToken
Verify a back-channel logout token the OP pushed to this application's `backchannel_logout_uri` (CONTRACT.md §12.7.3).
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).
webauthnAuthenticateFinish()  : WebauthnLoginResult
`POST /api/v1/auth/webauthn/authenticate/finish` (CONTRACT.md §24.1).
webauthnAuthenticateStart()  : WebauthnChallenge
`POST /api/v1/auth/webauthn/authenticate/start` (CONTRACT.md §24.1) — begin the **second-factor** ceremony.
webauthnDiscoverableFinish()  : WebauthnLoginResult
`POST /api/v1/auth/webauthn/authenticate/discoverable/finish` (CONTRACT.md §24.1).
webauthnDiscoverableStart()  : WebauthnChallenge
`POST /api/v1/auth/webauthn/authenticate/discoverable/start` (CONTRACT.md §24.1) — begin the usernameless ceremony.
webauthnPolicy()  : WebauthnPolicyApi
Per-tenant attestation policy governing the §24 ceremonies, and the compliance report over it.
webauthnRegisterFinish()  : WebauthnCredential
`POST /api/v1/auth/webauthn/register/finish` (CONTRACT.md §24.1) — hand the authenticator's answer back and store the credential.
webauthnRegisterStart()  : WebauthnChallenge
`POST /api/v1/auth/webauthn/register/start` (CONTRACT.md §24.1) — begin enrolling a passkey for the signed-in user.
webhooks()  : WebhooksApi
Outbound event notifications. Delivery signatures are verified with the §13 helper, which this namespace configures.

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 ][, string|null $oidcClientId = null ][, Sensitive|string|null $oidcClientSecret = null ][, string|null $oidcTenantId = null ][, string|null $expectedIssuer = null ][, string|null $expectedAudience = null ][, bool $retryEnabled = true ][, float $decisionMemoTtlMs = 0.0 ][, callable|null $telemetryHook = 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.

$oidcClientId : string|null = null

CONTRACT.md §12: the relying party's OAuth2 client_id, used by every oidc*/introspect/revoke operation and matched against an ID token's aud/azp (§12.4 rule 4). Required only by callers that use the §12 OIDC/SSO helpers — omitting it leaves the §1–§11 surface completely unaffected, and a §12 call without one raises AuthError before any wire call.

$oidcClientSecret : Sensitive|string|null = null

CONTRACT.md §12: the confidential client's client_secret, held behind Sensitive (§12.5). Omit for a public client — introspect/revoke/loginClientCredentials REQUIRE it (§12.1 note 4) and raise AuthError when it is absent; oidcExchange/ oidcRefresh omit it from the form body entirely when absent, per §12.1's "MUST omit rather than send empty/null" rule.

$oidcTenantId : string|null = null

CONTRACT.md §12.3 rule 4: the tenant UUID used as the default ?tenant_id= query parameter on /oauth2/* calls when a call does not supply one explicitly. $tenant above is a SLUG (§5's X-Tenant-ID header value) and is never accepted where the wire contract requires a UUID — a §12 call with neither this nor a per-call tenantId raises AuthError client-side, with no wire call.

$expectedIssuer : string|null = null

CONTRACT.md §10.1 rule 5: the iss value local token verification requires. CONDITIONAL and unset by default — null means no issuer check is performed at all; once supplied, a token whose iss differs (or which carries no iss) is rejected. There is no default value and no hardcoded AXIAM issuer anywhere in this SDK.

$expectedAudience : string|null = null

CONTRACT.md §10.1 rule 6: the aud value local token verification requires. CONDITIONAL and unset by default — null means no audience check at all; once supplied, a token whose aud does not contain it (including one with no aud) is rejected. An app guarding a user-facing resource server should generally expect axiam:user; it is not defaulted, because a service-to-service guard legitimately expects a different audience.

$retryEnabled : bool = true
$decisionMemoTtlMs : float = 0.0
$telemetryHook : callable|null = null

__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

close()

Releases this client's local resources (CONTRACT.md §18).

public close() : void

Idempotent — calling it twice is not an error. Cleanup runs from error paths, and an error path that itself throws hides the original failure.

This does not log out. §18.1 rule 5: shutting down a client releases local resources and never reaches the network. The server-side session deliberately outlives the client object, which is what lets a process restart and resume; a close() that logged out would silently end every user's session on each deploy. Call AxiamClient::logout() first if ending the session is what you want.

After this returns, every operation on this client throws a NetworkError rather than silently reconnecting.

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).

deviceAuthorize()

`POST /oauth2/device_authorization` (CONTRACT.md §14.1) — start the device grant and obtain the code pair.

public deviceAuthorize([string|null $scope = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : DeviceAuthorization

Unauthenticated by design: a device that cannot show a browser also cannot hold a client secret, so this never sends client_secret and never refuses a client built without one.

Parameters
$scope : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

when the discovery document advertises no device_authorization_endpoint.

Return values
DeviceAuthorization

deviceLogin()

The composed §14.3 helper: start the grant, hand the caller the user code (before the first poll), poll to completion.

public deviceLogin(callable(DeviceAuthorization): void $onUserCode[, string|null $scope = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ][, bool $adoptAsCredential = false ][, callable(int): void|null $sleep = null ]) : OidcTokenSet

Returns the token set; $adoptAsCredential is the same opt-in flag self::loginClientCredentials() uses (§14.3 rule 4, contract 1.7).

Parameters
$onUserCode : callable(DeviceAuthorization): void

Invoked before the first poll.

$scope : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
$adoptAsCredential : bool = false
$sleep : callable(int): void|null = null

Injectable sleeper, for tests.

Return values
OidcTokenSet

devicePoll()

`POST /oauth2/token` with the device-code grant (CONTRACT.md §14.1) — **one** poll attempt, for an application driving its own loop. Most callers want {@see self::deviceLogin()}.

public devicePoll(Sensitive|string $deviceCode[, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : OidcTokenSet
Parameters
$deviceCode : Sensitive|string
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Return values
OidcTokenSet

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

groups()

Named collections of users. Roles assigned to a group are inherited by every member.

public groups() : GroupsApi
Return values
GroupsApi

introspect()

`POST /oauth2/introspect` (RFC 7662, CONTRACT.md §12.1) — ask the server whether a token is active and, if so, for its metadata. Requires confidential-client credentials (§12.1 note 4). A `401` here is a client-credential failure surfaced as {@see \Axiam\Sdk\Core\OAuthProtocolError} and NEVER enters the §9 refresh guard (§12.3 rule 3).

public introspect(Sensitive|string $token[, string|null $tokenTypeHint = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : IntrospectionResult
Parameters
$token : Sensitive|string

The token to introspect — accepts the wrapped or bare form.

$tokenTypeHint : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

when no oidcClientSecret was configured.

Return values
IntrospectionResult

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

loginClientCredentials()

`POST /oauth2/token` with `grant_type=client_credentials` (CONTRACT.md §12.1) — service-account machine-to-machine login. Requests no `openid` scope, so the response carries no `id_token`. Pass `$adoptAsCredential: true` to additionally adopt the returned access token as this client's bearer credential for subsequent same-origin REST calls (§12.1, an opt-in MAY) — the token is held behind {@see Sensitive} inside {@see Session} and is NEVER sent to `/oauth2/*`.

public loginClientCredentials([string|null $scope = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ][, bool $adoptAsCredential = false ]) : OidcTokenSet
Parameters
$scope : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
$adoptAsCredential : bool = false
Tags
throws
AuthError

when no oidcClientSecret was configured — this grant cannot be performed by a public client (§12.1 note 4).

Return values
OidcTokenSet

loginOpaque()

`POST /api/v1/auth/opaque/login/start` followed by `/finish` — OPAQUE login, RFC 9807 (CONTRACT.md §23).

public loginOpaque(string $usernameOrEmail, string $password) : LoginResult

A sibling of self::login(), not a replacement. It takes the same arguments and returns the same LoginResult, MFA branch included, so an application can switch a tenant to OPAQUE without touching its own code.

What this does that login does not. The password never leaves this process. What crosses the wire is a blinded group element and a MAC, neither useful without the account's registration record and the tenant's OPRF seed — so a TLS-terminating proxy, an accidentally verbose request log, or a heap dump on the server cannot capture a plaintext password, because the server never has one. It also means a stolen record database is not offline-crackable on its own, which is the pre-computation resistance SRP could not offer. It does not protect against a compromised AXIAM server.

PHP is no longer doubly conditional. The SRP client this replaces needed a bignum extension and a tenant on pbkdf2_sha256, because no PHP runtime offers Argon2id with a caller-supplied salt — so the AXIAM default was, for PHP, unreachable. The key stretching now happens inside libaxiam_opaque_ffi, so the only remaining condition is that the library and ext-ffi are present, which self::opaqueAvailable() reports.

One round trip, and no server-proof step. SRP had to guess a group before the server named one and restart the exchange if it guessed wrong; KE1 does not depend on the key-stretching function. And where the old §23.3 rule 6 had to mandate an M2 check in capitals — because skipping it kept only half the protocol — RFC 9807's AKE authenticates the server during the handshake, so opening KE2 is the proof that it holds the record.

Zeroization. PHP strings are immutable and the runtime copies them freely, so this SDK cannot clear the password — §23.3 rule 8 requires saying so rather than implying a guarantee it cannot keep.

A failed KE2 is not always the end (§23.4 rule 7, contract 1.29). Nothing is ever sent to login/finish when the envelope does not open, but what happens next depends on the mode the login/start response named, and on nothing else. Under "optional" this method retries over self::login() with the same credentials and returns that call's outcome — its success, or its error. Under "required", and for a response that carried no mode at all (a server older than the field), the failure is an AuthError and there is no retry. optional is the state a tenant lives in for the whole of a migration: every account has no registration record the moment OPAQUE is enabled and acquires one only when its password is next set, so treating the failed exchange as final would lock out every user of the tenant. See OpaqueMode for why mode is not downgrade protection.

Parameters
$usernameOrEmail : string
$password : string
Tags
throws
NetworkError

if the tenant has OPAQUE disabled (the endpoint answers 404 — a property of the tenant, not of any user), if ext-ffi or libaxiam_opaque_ffi is absent, or if the server names a key-stretching function this SDK cannot ask for. Deliberately not AuthError: reporting a configuration gap as a credential failure would send a user off to reset a password that works, and would stop a caller falling back to self::login()

AuthError

for a wrong password, an account that does not exist, and a server that does not hold the record — indistinguishable by design. Nothing is sent to login/finish in that case (§23.4 rule 7). Raised under mode: "required" and under a response with no mode; under "optional" the exchange is retried over self::login() first and this carries that call's failure instead

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

logoutUrl()

Build the RP-initiated logout URL to redirect the user agent to (CONTRACT.md §12.7.2). Does **not** clear this client's own session.

public logoutUrl(Sensitive|string $idToken[, string|null $postLogoutRedirectUri = null ][, string|null $state = null ][, OidcConfiguration|null $configuration = null ]) : string
Parameters
$idToken : Sensitive|string
$postLogoutRedirectUri : string|null = null
$state : string|null = null
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

when the discovery document advertises no end_session_endpoint.

Return values
string

management()

The CONTRACT.md §27 management surface: 147 operations across 24 namespaces.

public management() : ManagementApi

$client->management()->users()->listItems(). Built on the same Guzzle client that carries AuthMiddleware and RefreshMiddleware, so §27.8's "the generated layer sits on the SDK's existing request path" holds by construction rather than by convention.

Memoised: the returned object holds only the transport and the client's default scope, and handing back a new one per call would make management() !== management() for no benefit. This is not a §27.4 rule 10 violation — that rule forbids caching RESPONSES, and nothing here caches one.

Return values
ManagementApi

mfaConfirm()

`POST /api/v1/auth/mfa/confirm` (CONTRACT.md §25.1) — activate the factor {@see self::mfaEnroll()} offered. Returns whether MFA is now enabled.

public mfaConfirm(string $totpCode) : bool
Parameters
$totpCode : string
Return values
bool

mfaEnroll()

`POST /api/v1/auth/mfa/enroll` (CONTRACT.md §25.1) — start voluntary TOTP enrolment for the signed-in user.

public mfaEnroll() : MfaEnrollment

Changes nothing about the current session. In particular it does not clear the §17 decision memo: the subject has not changed, and discarding a warm memo on an unrelated profile action costs a round trip on every check that follows (§25.2 rule 3).

Return values
MfaEnrollment

mfaSetupConfirm()

`POST /api/v1/auth/mfa/setup/confirm` (CONTRACT.md §25.1) — finish forced enrolment and, with it, the login that was interrupted.

public mfaSetupConfirm(Sensitive|string $setupToken, string $totpCode) : LoginResult

Adopts credentials exactly as self::login() does, because it is the completion of a login (§25.2 rule 2) — including capturing the session's first CSRF token.

Parameters
$setupToken : Sensitive|string
$totpCode : string
Return values
LoginResult

mfaSetupEnroll()

`POST /api/v1/auth/mfa/setup/enroll` (CONTRACT.md §25.1) — start the enrolment a {@see self::login()} demanded.

public mfaSetupEnroll(Sensitive|string $setupToken) : MfaEnrollment

Reached when login() returns LoginResult::$mfaSetupRequired: the tenant requires MFA and this account has none. There is no session yet — the setup token is the credential.

Parameters
$setupToken : Sensitive|string
Return values
MfaEnrollment

oidcBegin()

Build an authorization request (CONTRACT.md §12.1) — **pure local computation, no network I/O**. Generates a `state`/`nonce` (CSPRNG, ≥128 bits) and a fresh PKCE verifier/challenge pair (**S256 only**), and builds `$configuration`'s `authorization_endpoint` into a redirect URL with exactly the eight SDK-owned query parameters plus any `$extraParams` supplied.

public oidcBegin(OidcConfiguration $configuration, string $redirectUri[, string|array<int, string>|null $scope = null ][, array<string, string> $extraParams = [] ]) : AuthorizationRequest

Nothing is stored (§12.3 rule 1): persist the returned state, nonce and codeVerifier yourself (e.g. in your own HTTP session, or via MemoryOidcStateStore) and pass nonce/codeVerifier back into self::oidcExchange() when the authorization code arrives.

Parameters
$configuration : OidcConfiguration
$redirectUri : string
$scope : string|array<int, string>|null = null

openid is added automatically when absent (§12.1 rule 4). Defaults to openid.

$extraParams : array<string, string> = []

Extra authorization-request parameters (e.g. prompt, login_hint). Throws InvalidArgumentException if one tries to override an SDK-owned parameter (§12.1 rule 5).

Return values
AuthorizationRequest

oidcDiscover()

`GET /.well-known/openid-configuration` (CONTRACT.md §12.1) — fetch the OIDC discovery document, cached per origin with a ≥5-minute TTL and single-flight de-duplication of concurrent callers (§12.3 rule 6). The document's own `issuer` is authoritative for ID-token validation (§12.4 rule 3) and may legitimately differ from `$baseUrl` behind a proxy — never treated as an error (§12.3 rule 6).

public oidcDiscover() : OidcConfiguration
Return values
OidcConfiguration

oidcExchange()

`POST /oauth2/token` with `grant_type=authorization_code` (CONTRACT.md §12.1) — exchange an authorization code for a token set, validating the returned ID token in full (§12.4) before returning. `$nonce` is MANDATORY: this grant always requests `openid`, so §12.4 rule 6 always applies. On ANY §12.4 failure the whole token set is discarded and {@see AuthError} is raised with the matching reason code (§12.4 rule 7) — `getReason()` returns one of `invalid_alg`, `unknown_kid`, `invalid_signature`, `invalid_issuer`, `invalid_audience`, `token_expired`, `nonce_mismatch`.

public oidcExchange(string $code, Sensitive|string $codeVerifier, string $redirectUri, string $nonce[, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : OidcTokenSet
Parameters
$code : string
$codeVerifier : Sensitive|string

The verifier from the matching AuthorizationRequest — accepts the wrapped or bare form.

$redirectUri : string
$nonce : string
$tenantId : string|null = null

Tenant UUID for the required ?tenant_id= query parameter (§12.3 rule 4). Falls back to the client's oidcTenantId.

$configuration : OidcConfiguration|null = null

A pre-fetched discovery document, to avoid re-reading the (cached) one. Fetched via self::oidcDiscover() when omitted.

Return values
OidcTokenSet

oidcPar()

`POST /oauth2/par` (CONTRACT.md §26.1) — push the authorization request over the back channel and get an opaque handle to redirect with.

public oidcPar(AuthorizationRequest $request, string $redirectUri[, OidcConfiguration|null $configuration = null ][, string|array<int, string>|null $scope = null ][, string|null $tenantId = null ]) : PushedAuthorizationRequest

PAR moves the authorization request off the browser: instead of putting scope, redirect_uri, state and the PKCE challenge into a URL the user agent carries, the client POSTs them straight to AXIAM and puts an opaque request_uri in the redirect, so what travels through the browser is a random string that cannot be edited into meaning something else.

Required for a FAPI 2.0 client (§21.1). Not retried, being a POST that creates server state (§26.2 rule 4).

Parameters
$request : AuthorizationRequest
$redirectUri : string
$configuration : OidcConfiguration|null = null
$scope : string|array<int, string>|null = null
$tenantId : string|null = null
Return values
PushedAuthorizationRequest

oidcRefresh()

`POST /oauth2/token` with `grant_type=refresh_token` (CONTRACT.md §12.1) — refresh an {@see OidcTokenSet} under the SAME §9 single-flight guard {@see self::refresh()} uses. A **distinct operation** from {@see self::refresh()} (the cookie/opaque-token session path) — the two are never merged, aliased, or fall back to one another, but they share ONE guard slot: a concurrent `oidcRefresh()` call finding the guard busy with a cookie-session refresh retries (bounded) rather than returning a stale token set.

public oidcRefresh(Sensitive|string $refreshToken[, string|null $scope = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : OidcTokenSet

Any id_token in the response is validated against §12.4 rules 1–5 and 7; rule 6 (nonce) is skipped (OIDC Core §12.2 does not require a nonce on a refresh-issued ID token).

Parameters
$refreshToken : Sensitive|string

The refresh token to redeem — accepts the wrapped or bare form.

$scope : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Return values
OidcTokenSet

opaqueAvailable()

Whether this installation can perform OPAQUE (§23.2).

public opaqueAvailable() : bool

PHP remains the AXIAM SDK language where this genuinely answers false — but for a different and simpler reason than before. The SRP equivalent was false when neither ext-gmp nor ext-bcmath was present, and even a true there did not promise every tenant would work: an argon2id tenant was refused at login time, because no PHP runtime offers Argon2id with a caller-supplied salt. That second condition is gone. The key stretching happens inside libaxiam_opaque_ffi, so a true here means every tenant works.

Return values
bool

opaqueEnrollment()

Builds a registration record for `$password`, to send with any request that sets one: `POST /api/v1/users`, `/auth/password/change`, `/auth/reset/confirm` and `/admin/bootstrap`.

public opaqueEnrollment(string $password) : OpaqueEnrollment

The server cannot build this — it never sees the plaintext — so it has to arrive with the request or not at all.

Unlike the srpEnrollment it replaces this performs I/O: one register/start round trip. OPAQUE's envelope is sealed under the server's oblivious PRF, so there is no offline computation that produces a valid record.

Note the parameters that are gone. There is no $identity: the SRP version required the account's USERNAME, and an email there produced a verifier no login could ever satisfy, whereas a record binds to a credential identifier the server chooses. And there is no $group or $params, because those come from the register/start response — a caller cannot pick a cost the server will not honour.

Parameters
$password : string
Tags
throws
NetworkError

if the tenant has OPAQUE disabled, if ext-ffi or libaxiam_opaque_ffi is absent, or if the server names a key-stretching function this SDK cannot ask for

Return values
OpaqueEnrollment

opaqueEnrollmentForSelf()

Builds a registration record for the **caller's own** new password, sealed against the tenant the caller's account lives in.

public opaqueEnrollmentForSelf(string $password) : OpaqueEnrollment

CONTRACT.md §5.2.2 rule 2. POST /auth/password/change and the record that accompanies it are about the account, not about whatever tenant the client is currently pointed at, and a record sealed against the acting tenant is refused with "the OPAQUE session was issued for a different tenant".

The distinction only bites for an organization-level principal that has selected another tenant to act on; for everyone else the two tenants are the same value and this behaves identically to self::opaqueEnrollment(). It is still the method to call for a self-service password change, because which principal is signed in is not something the call site usually knows.

Parameters
$password : string
Tags
throws
NetworkError

when no login has completed on this client yet — the principal tenant is reported by the login response, so there is nothing to seal against before then — and on the same terms as self::opaqueEnrollment() otherwise.

Return values
OpaqueEnrollment

passwordResetContext()

`GET /api/v1/auth/reset/context` (CONTRACT.md §25.1) — the OPAQUE policy for the account a reset token belongs to.

public passwordResetContext(Sensitive|string $token) : PasswordResetContext

Call this before self::confirmPasswordReset() on any tenant that might have §23 enabled: the client has to build a registration record, and building one needs parameters it cannot know before it has a token to ask with. Sending a plaintext password to a tenant in opaque_mode: required is refused, and refused late (§25.4 rule 1).

A 404 means unknown, expired or already-consumed, deliberately without distinguishing them; this SDK does not distinguish them either (§25.4 rule 3).

Parameters
$token : Sensitive|string
Return values
PasswordResetContext

platform()

Deployment-level probes and FIDO metadata state. Unauthenticated where the server leaves them so.

public platform() : PlatformApi
Return values
PlatformApi

privacy()

GDPR self-service: the authenticated account's own export and erasure. Scoped to the caller, never to another user.

public privacy() : PrivacyApi
Return values
PrivacyApi

reactors()

Registration of §22 AMQP extension actors -- the admin surface §22.9 describes, which no SDK could previously reach.

public reactors() : ReactorsApi
Return values
ReactorsApi

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

requestPasswordReset()

`POST /api/v1/auth/reset` (CONTRACT.md §25.1) — ask for a reset mail.

public requestPasswordReset(PasswordResetRequest $request) : void

Returns normally whether or not the address exists, and this SDK exposes no way to tell the two apart. That is not an omission to improve on: a client that surfaced a "no such user" state — even one inferred from timing — would turn the endpoint into the account-enumeration oracle its uniform response exists to prevent (§25.4).

Parameters
$request : PasswordResetRequest

resendOwnVerification()

`POST /api/v1/users/me/resend-verification` (CONTRACT.md §25.1, §25.7) — resend the **signed-in caller's own** verification mail, and say what happened.

public resendOwnVerification() : void

Takes no address. The server reads it off the caller's own record, and this signature deliberately offers no way to name a different one: a parameter here would let an authenticated session mail an arbitrary address.

Unlike self::resendVerification() this reports the outcome, because the caller is signed in to the account it is asking about and none of the outcomes tells it anything it did not already bring with it:

  • returns — a token was minted and the mail enqueued. Delivery is asynchronous and can still fail at the provider; a queue that accepts everything in front of a provider that rejects it looks exactly like this succeeding (§25.7 rule 3).
  • 409 — already verified, or the account is in a state that must not be sent a live token. Raised as the §2 mapping of 409.
  • 429 — the daily resend limit. Raised as the §2 mapping of 429.

§25.7 rule 2 forbids falling back to the unauthenticated endpoint on either failure, and this SDK does not: that fallback turns both back into a silent success and restores the bug this operation exists to fix, with an extra round trip.

Tags
throws
AuthError

when there is no session — raised client-side, with no wire call.

AuthzError

on the 409: already verified, or an account state that must not be sent a live token.

NetworkError

on the 429: the daily resend limit is reached.

resendVerification()

`POST /api/v1/auth/resend-verification` (CONTRACT.md §25.1) — the **unauthenticated** resend, for a caller with no session.

public resendVerification(string $email, string $tenantId) : void

Returns normally whatever the outcome. The address may not exist, may already be verified, or may be over the daily limit, and the server answers identically in every case because it takes an address from an anonymous caller: anything else is an oracle for which addresses have accounts (§25.4).

A caller that is signed in wants self::resendOwnVerification(), which says what happened. §25.7 rule 2 forbids routing either of these to the other, and this SDK does not.

Parameters
$email : string
$tenantId : string

resolvedOrgId()

The organization UUID §27 routes substitute for `{org_id}`, or `null` when the client was constructed without one (§27.4 rule 3).

public resolvedOrgId() : string|null

Public because §27 has routes where {org_id} names the entity being ADMINISTERED rather than the calling context — the signing CAs under caCertificates — and those take it as an ordinary argument. Without this accessor a caller had no way to pass the same identifier the implicit routes use.

Return values
string|null

resolvedTenantId()

The tenant UUID §27 routes substitute for `{tenant_id}`, or `null` when the client was constructed without one (§27.4 rule 3).

public resolvedTenantId() : string|null

This is the UUID, never the $tenant SLUG the client is constructed with: §5's X-Tenant-ID header takes the slug, but a {tenant_id} path segment takes the UUID, and the two are not interchangeable.

Return values
string|null

revoke()

`POST /oauth2/revoke` (RFC 7009, CONTRACT.md §12.1) — revoke an access or refresh token. Returns nothing. Per RFC 7009 the server answers `200` for an unknown, expired, or already-revoked token too, so this call is **idempotent**: only a `401` (client authentication failed) is an error, surfaced as {@see \Axiam\Sdk\Core\OAuthProtocolError} (§12.1 note 5). A `5xx` still raises {@see NetworkError}.

public revoke(Sensitive|string $token[, string|null $tokenTypeHint = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : void
Parameters
$token : Sensitive|string

The token to revoke — accepts the wrapped or bare form.

$tokenTypeHint : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

when no oidcClientSecret was configured.

ssoComplete()

`POST /api/v1/auth/federation/oidc/callback` (CONTRACT.md §12.1) — step 2 of upstream SSO: consumes the single-use `$state`, provisions or links the user, and establishes the session. The session arrives as `Set-Cookie` — not in the response body (§12.1 note 6) — so it is captured automatically via this client's shared §4 cookie jar; the freshly-issued §3 CSRF token is captured too, exactly as {@see self::login()} does. §12.4 does not apply here: no ID token ever reaches the SDK on the federation path.

public ssoComplete(string $state, string $code) : SsoCompleteResult
Parameters
$state : string
$code : string
Return values
SsoCompleteResult

ssoCompleteHandoff()

`POST /api/v1/auth/federation/handoff` (CONTRACT.md §12.1, contract 1.38) — redeem the single-use `axiam_handoff` code the SAML and Apple flows deliver.

public ssoCompleteHandoff(string $code) : SsoCompleteResult

Valid 60 s and redeemable once. Redeem it from the same origin, immediately, and never retry a failed redemption: a 401 is terminal, and this makes exactly one wire call so that it cannot become a retry by accident (§12.1 note 12).

Parameters
$code : string
Return values
SsoCompleteResult

ssoCompleteOauth2()

`POST /api/v1/auth/federation/oauth2/callback` (CONTRACT.md §12.1, contract 1.38) — step 2 of a plain-OAuth2 login. Same `Set-Cookie` session and same §3 CSRF capture as {@see self::ssoComplete()}. §12.4 does not apply: an `OAuth2` provider issues no ID token at all (§12.1 note 11).

public ssoCompleteOauth2(string $state, string $code) : SsoCompleteResult
Parameters
$state : string
$code : string
Return values
SsoCompleteResult

ssoProviders()

`GET /api/v1/auth/federation/providers` (CONTRACT.md §12.1, contract 1.38) — which "Sign in with X" buttons to render for a workspace. The identifiers travel as **query** parameters; this is a `GET` and sends no body.

public ssoProviders([string|null $orgId = null ][, string|null $orgSlug = null ][, string|null $tenantId = null ][, string|null $tenantSlug = null ]) : FederationProviderList

An empty list is a success. An unknown organization, a known one with nothing configured, and a request naming no workspace at all all answer 200 with an empty array (§12.1 note 9), precisely so the endpoint cannot be used to enumerate organization or tenant slugs. For the same reason this is the one federation operation that does not throw client-side when no workspace resolves.

Parameters
$orgId : string|null = null
$orgSlug : string|null = null
$tenantId : string|null = null
$tenantSlug : string|null = null
Return values
FederationProviderList

ssoStart()

`POST /api/v1/auth/federation/oidc/start` (CONTRACT.md §12.1) — step 1 of first-time SSO against an **upstream** IdP. No JWT required. One tenant form (`$tenantId`/`$tenantSlug`) and one org form (`$orgId`/`$orgSlug`) must be resolvable, from the arguments or from this client's construction options (§5.1) — an unresolvable one raises {@see AuthError} client-side, with no wire call. Redirect the browser to the returned `authorizeUrl` and round-trip `state` back into {@see self::ssoComplete()} unmodified; the server keeps the nonce to itself (§12.1 note 7).

public ssoStart(string $federationConfigId, string $redirectUri[, string|null $tenantId = null ][, string|null $tenantSlug = null ][, string|null $orgId = null ][, string|null $orgSlug = null ]) : SsoStartResult
Parameters
$federationConfigId : string
$redirectUri : string
$tenantId : string|null = null
$tenantSlug : string|null = null
$orgId : string|null = null
$orgSlug : string|null = null
Return values
SsoStartResult

ssoStartOauth2()

`POST /api/v1/auth/federation/oauth2/start` (CONTRACT.md §12.1, contract 1.38) — step 1 of a login through a **plain-OAuth2** upstream (GitHub, Facebook, `generic_oauth2`). Call this, rather than {@see self::ssoStart()}, exactly when the provider's `protocol` is `OAuth2` (§12.1 note 10).

public ssoStartOauth2(string $federationConfigId, string $redirectUri[, string|null $tenantId = null ][, string|null $tenantSlug = null ][, string|null $orgId = null ][, string|null $orgSlug = null ]) : SsoStartResult

PKCE is mandatory on this path and is generated and held server-side (§12.1 note 11). A 400 can mean the $redirectUri is not on an origin the deployment accepts (§12.1 rule 12a) and surfaces as NetworkError; it is not retried.

Parameters
$federationConfigId : string
$redirectUri : string
$tenantId : string|null = null
$tenantSlug : string|null = null
$orgId : string|null = null
$orgSlug : string|null = null
Return values
SsoStartResult

tenants()

Tenants within an organization -- the isolation boundary every other namespace is scoped to.

public tenants() : TenantsApi
Return values
TenantsApi

tokenExchange()

`POST /oauth2/token` with the RFC 8693 grant (CONTRACT.md §15.1) — exchange a token for a **narrower** one.

public tokenExchange(Sensitive|string $subjectToken, string $subjectTokenType[, Sensitive|string|null $actorToken = null ][, array<int, string>|null $scopes = null ][, string|null $audience = null ][, string|null $resource = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : ExchangedToken

Requires confidential-client credentials. Never defaults $actorToken, never auto-narrows after invalid_scope, never adopts the result.

Parameters
$subjectToken : Sensitive|string
$subjectTokenType : string

What kind of token $subjectToken is. Required (§15.1): OidcClient::ACCESS_TOKEN_TYPE for an AXIAM access token, or OidcClient::JWT_TOKEN_TYPE for a trusted external issuer's JWT (§15.7). Never inferred from the token.

$actorToken : Sensitive|string|null = null
$scopes : array<int, string>|null = null
$audience : string|null = null
$resource : string|null = null
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

when no oidcClientSecret was configured.

Return values
ExchangedToken

umaChallengeHeader()

Format a `WWW-Authenticate: UMA` header (CONTRACT.md §20.3, emit half).

public umaChallengeHeader(string $realm, string $asUri, Sensitive|string $ticket) : string
Parameters
$realm : string
$asUri : string
$ticket : Sensitive|string
Return values
string

umaDeleteResource()

`DELETE /uma2/rreg/resource_set/{id}` (CONTRACT.md §20.1).

public umaDeleteResource(Sensitive|string $pat, string $id) : void
Parameters
$pat : Sensitive|string
$id : string

umaExchangeTicket()

The UMA ticket grant (CONTRACT.md §20.1) — redeem a permission ticket for an RPT.

public umaExchangeTicket(Sensitive|string $ticket, Sensitive|string $claimToken[, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : RequestingPartyToken

Never retried (§20.2 rule 6, the one documented exception to §16): a ticket is spent whether or not the exchange succeeded, so a retry is a second redemption. A failure surfaces; request a new ticket. The result is never adopted as this client's credentials and carries no refresh token.

Parameters
$ticket : Sensitive|string
$claimToken : Sensitive|string
$tenantId : string|null = null
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

when no oidcClientSecret was configured.

Return values
RequestingPartyToken

umaListResources()

`GET /uma2/rreg/resource_set` (CONTRACT.md §20.1) — the ids this client registered.

public umaListResources(Sensitive|string $pat) : array<int, string>
Parameters
$pat : Sensitive|string
Return values
array<int, string>

umaParseChallenge()

Parse a `WWW-Authenticate: UMA …` header (CONTRACT.md §20.3) — local computation, and deliberately **no** exchange of the ticket it finds.

public umaParseChallenge(string $header) : UmaChallenge|null
Parameters
$header : string
Return values
UmaChallenge|null

umaRegisterResource()

`POST /uma2/rreg/resource_set` (CONTRACT.md §20.1) — register a UMA resource set.

public umaRegisterResource(Sensitive|string $pat, string $name[, string|null $type = null ][, array<int, string> $resourceScopes = [] ]) : ResourceSet

The returned id is the AXIAM resource id, usable directly as a RequestedPermission's $resourceId.

Parameters
$pat : Sensitive|string

A client-credentials token carrying uma_protection (§20.2 rule 1) — never this client's session token.

$name : string
$type : string|null = null
$resourceScopes : array<int, string> = []
Return values
ResourceSet

umaUpdateResource()

`PUT /uma2/rreg/resource_set/{id}` (CONTRACT.md §20.1) — replace a resource set.

public umaUpdateResource(Sensitive|string $pat, string $id, string $name[, string|null $type = null ][, array<int, string> $resourceScopes = [] ]) : ResourceSet

$resourceScopes replaces the declared list rather than merging with it (§20.2 rule 8), so omitting a scope removes it.

Parameters
$pat : Sensitive|string
$id : string
$name : string
$type : string|null = null
$resourceScopes : array<int, string> = []
Return values
ResourceSet

users()

Users within the client's tenant, and the administrative side of their second factor and lockout state.

public users() : UsersApi
Return values
UsersApi

verifyEmail()

`POST /api/v1/auth/verify-email` (CONTRACT.md §25.1).

public verifyEmail(Sensitive|string $token, string $tenantId) : void

Unauthenticated: a user whose address is unverified may have no session at all. $tenantId is a body field here — this is not an /oauth2 endpoint, so §12.1 rule 2's query-parameter convention does not reach it.

Parameters
$token : Sensitive|string
$tenantId : string

verifyLocally()

Verify an INBOUND caller's token and nothing else — the seam every request guard must use (CONTRACT.md §10.1 rule 8). Delegates straight to {@see JwksVerifier::verify()}, which applies the full §10.1 minimum local-verification set, and returns `null` on any failure with **no fallback to another credential**.

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

This is deliberately the only verification entry point offered to the framework bridges. Its sibling self::verifyLocallyOrFallback() substitutes this client's own session when verification fails, which is correct for the SDK's outbound calls and an authentication bypass in a request guard — see SEC-085.

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

Verified claims of the SUPPLIED token, or null.

verifyLocallyOrFallback()

Local-first JWT verification with a reactive-refresh fallback (D-02) — for the SDK's own OUTBOUND calls, where the token being verified is this client's own and refreshing it is the intended recovery. Tries {@see 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.

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

Never call this from a request guard. The fallback re-verifies a different credential — this client's own session, typically a service account — so a caller presenting an expired, foreign-tenant or forged token would be admitted under the application's own identity (SEC-085). Request guards must call self::verifyLocally(), which decides on the caller's credential alone.

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

Verified claims, or null.

verifyLogoutToken()

Verify a back-channel logout token the OP pushed to this application's `backchannel_logout_uri` (CONTRACT.md §12.7.3).

public verifyLogoutToken(string $logoutToken[, OidcConfiguration|null $configuration = null ]) : VerifiedLogoutToken

Returns the sid/sub/jti the token names — never a bare bool, because the RP has to know which session to end. Dedup on jti yourself: delivery is at-least-once.

Parameters
$logoutToken : string
$configuration : OidcConfiguration|null = null
Tags
throws
AuthError

on any failed check.

Return values
VerifiedLogoutToken

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

webauthnAuthenticateFinish()

`POST /api/v1/auth/webauthn/authenticate/finish` (CONTRACT.md §24.1).

public webauthnAuthenticateFinish(Sensitive|string $stateToken, string $response) : WebauthnLoginResult

On success the client is signed in: the server sets the same cookie triple POST /api/v1/auth/login sets, and the §17 decision memo is cleared because the subject changed (§24.3).

Parameters
$stateToken : Sensitive|string
$response : string
Return values
WebauthnLoginResult

webauthnAuthenticateStart()

`POST /api/v1/auth/webauthn/authenticate/start` (CONTRACT.md §24.1) — begin the **second-factor** ceremony.

public webauthnAuthenticateStart(Sensitive|string $challengeToken) : WebauthnChallenge

Continues a self::login() that answered mfaRequired with "webauthn" among its available methods; $challengeToken is that login's token. A different flow from self::webauthnDiscoverableStart(), not the same one with a flag (§24.2) — which is why the token is required here and absent there.

Parameters
$challengeToken : Sensitive|string
Return values
WebauthnChallenge

webauthnDiscoverableFinish()

`POST /api/v1/auth/webauthn/authenticate/discoverable/finish` (CONTRACT.md §24.1).

public webauthnDiscoverableFinish(Sensitive|string $stateToken, string $response) : WebauthnLoginResult

Adopts credentials exactly as self::webauthnAuthenticateFinish() does.

Parameters
$stateToken : Sensitive|string
$response : string
Return values
WebauthnLoginResult

webauthnDiscoverableStart()

`POST /api/v1/auth/webauthn/authenticate/discoverable/start` (CONTRACT.md §24.1) — begin the usernameless ceremony.

public webauthnDiscoverableStart([WebauthnWorkspace|null $workspace = null ]) : WebauthnChallenge

A primary factor: nothing precedes it, allowCredentials comes back empty, and the assertion itself identifies the user. Pass null for $workspace to have it filled from this client's own configured identity.

Unlike authenticate/finish, discoverable/finish fires the login.post_auth reactor hook (§22.5) — the former continues a login already gated at its password step, and this one has no such step.

Parameters
$workspace : WebauthnWorkspace|null = null
Return values
WebauthnChallenge

webauthnRegisterFinish()

`POST /api/v1/auth/webauthn/register/finish` (CONTRACT.md §24.1) — hand the authenticator's answer back and store the credential.

public webauthnRegisterFinish(Sensitive|string $stateToken, string $credentialName, string $response) : WebauthnCredential

$response is the platform's own response JSON, verbatim (§24.6a rule 2): credential.toJSON() from a browser, or registrationResponseJson from Android's Credential Manager relayed by a mobile client. It reaches the wire byte for byte, because re-encoding a signed buffer is three chances to corrupt it in service of nothing.

Parameters
$stateToken : Sensitive|string
$credentialName : string
$response : string
Return values
WebauthnCredential

webauthnRegisterStart()

`POST /api/v1/auth/webauthn/register/start` (CONTRACT.md §24.1) — begin enrolling a passkey for the signed-in user.

public webauthnRegisterStart() : WebauthnChallenge

Requires a session, and refuses client-side with no wire call when there is none — the shape §1.1 rule 3 requires of getUserInfo.

The returned options are the server's, untouched (§24.0). A 503 here means the tenant's attestation policy needs FIDO metadata the server cannot reach: a configuration state, not a transient one, and §24.4 rule 2 deliberately does not retry it.

Return values
WebauthnChallenge

webhooks()

Outbound event notifications. Delivery signatures are verified with the §13 helper, which this namespace configures.

public webhooks() : WebhooksApi
Return values
WebhooksApi
On this page

Search results