AxiamClient
in package
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):
$plainHttpcarries 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/refreshPOST (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 forlogin()/verifyMfa()/logout()— a failed login/logout attempt (401/403) must surface as its own clear error, not trigger an unrelated token-refresh attempt first.$authzHttpcarries 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_idconstructor 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.$clientCertand$clientKeyare 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 totruewhen$grpcTargetis not supplied (there would be nothing to connect the gRPC transport to) andfalseotherwise — an explicittrue/falsealways 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 internalHandlerStacks instead of Guzzle's default cURL/stream handler. Mirrors the C# sibling SDK'sCreateForTestinginternal seam, adapted to Guzzle's own documentedHandlerStack::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 everyoidc*/introspect/revokeoperation and matched against an ID token'saud/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/loginClientCredentialsREQUIRE it (§12.1 note 4) and raise AuthError when it is absent;oidcExchange/oidcRefreshomit 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.$tenantabove is a SLUG (§5'sX-Tenant-IDheader value) and is never accepted where the wire contract requires a UUID — a §12 call with neither this nor a per-calltenantIdraises AuthError client-side, with no wire call. - $expectedIssuer : string|null = null
-
CONTRACT.md §10.1 rule 5: the
issvalue local token verification requires. CONDITIONAL and unset by default —nullmeans no issuer check is performed at all; once supplied, a token whoseissdiffers (or which carries noiss) 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
audvalue local token verification requires. CONDITIONAL and unset by default —nullmeans no audience check at all; once supplied, a token whoseauddoes not contain it (including one with noaud) is rejected. An app guarding a user-facing resource server should generally expectaxiam: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
audit()
Append-only audit log, read-only by construction.
public
audit() : AuditApi
Return values
AuditApibatchCheck()
`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>caCertificates()
Organization CAs and the per-tenant signing CAs chained beneath them.
public
caCertificates() : CaCertificatesApi
Return values
CaCertificatesApican()
`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
boolcertificates()
End-entity X.509 certificates -- the ones issued to users, services and IoT devices.
public
certificates() : CertificatesApi
Return values
CertificatesApicheckAccess()
`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
AxiamClientinstance (typically authenticated as a service account, or not authenticated at all) to authorize each inbound HTTP request's OWN end user: passingsubjectId: $endUserIdhere checks the end user's permissions, never the shared client's.null(the default) preserves this method's pre-§11 behavior exactly.
Return values
boolclose()
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.
confirmPasswordReset()
`POST /api/v1/auth/reset/confirm` (CONTRACT.md §25.1) — set the new password.
public
confirmPasswordReset(PasswordResetConfirmation $confirmation) : void
Parameters
- $confirmation : PasswordResetConfirmation
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
Return values
DeviceAuthorizationdeviceLogin()
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
OidcTokenSetdevicePoll()
`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
OidcTokenSetemailConfig()
Transactional-mail transport, configurable at organization level and overridable per tenant.
public
emailConfig() : EmailConfigApi
Return values
EmailConfigApifederation()
Upstream IdP configuration and the per-user links it produces.
public
federation() : FederationApi
Return values
FederationApigetUserInfo()
`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
UserInfogroups()
Named collections of users. Roles assigned to a group are inherited by every member.
public
groups() : GroupsApi
Return values
GroupsApiintrospect()
`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
Return values
IntrospectionResultlogin()
`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
LoginResultloginClientCredentials()
`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
Return values
OidcTokenSetloginOpaque()
`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
Return values
LoginResultlogout()
`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
Return values
stringmanagement()
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
ManagementApimfaConfirm()
`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
boolmfaEnroll()
`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
MfaEnrollmentmfaSetupConfirm()
`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
LoginResultmfaSetupEnroll()
`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
MfaEnrollmentnotificationRules()
Which events raise a notification, and to whom.
public
notificationRules() : NotificationRulesApi
Return values
NotificationRulesApioauth2Clients()
Registered OAuth2/OIDC clients -- the registration half of what §12, §21 and §26 then speak to.
public
oauth2Clients() : Oauth2ClientsApi
Return values
Oauth2ClientsApioidcBegin()
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
-
openidis added automatically when absent (§12.1 rule 4). Defaults toopenid. - $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
AuthorizationRequestoidcDiscover()
`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
OidcConfigurationoidcExchange()
`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'soidcTenantId. - $configuration : OidcConfiguration|null = null
-
A pre-fetched discovery document, to avoid re-reading the (cached) one. Fetched via self::oidcDiscover() when omitted.
Return values
OidcTokenSetoidcPar()
`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
PushedAuthorizationRequestoidcRefresh()
`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
OidcTokenSetopaqueAvailable()
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
boolopaqueEnrollment()
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
Return values
OpaqueEnrollmentopaqueEnrollmentForSelf()
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
Return values
OpaqueEnrollmentorganizations()
Organizations an SDK client may read and configure. Creation and deletion are outside the SDK boundary (§27.0).
public
organizations() : OrganizationsApi
Return values
OrganizationsApipasswordResetContext()
`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
PasswordResetContextpermissions()
Permissions -- an action on a resource, optionally narrowed by a scope.
public
permissions() : PermissionsApi
Return values
PermissionsApipgpKeys()
OpenPGP keys used for audit signing and encrypted data export.
public
pgpKeys() : PgpKeysApi
Return values
PgpKeysApiplatform()
Deployment-level probes and FIDO metadata state. Unauthenticated where the server leaves them so.
public
platform() : PlatformApi
Return values
PlatformApiprivacy()
GDPR self-service: the authenticated account's own export and erasure. Scoped to the caller, never to another user.
public
privacy() : PrivacyApi
Return values
PrivacyApireactors()
Registration of §22 AMQP extension actors -- the admin surface §22.9 describes, which no SDK could previously reach.
public
reactors() : ReactorsApi
Return values
ReactorsApirefresh()
`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 of409.429— the daily resend limit. Raised as the §2 mapping of429.
§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
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|nullresolvedTenantId()
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|nullresources()
The resource hierarchy role assignments cascade down.
public
resources() : ResourcesApi
Return values
ResourcesApirevoke()
`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
roles()
Roles, their permission sets, and their assignment to users and groups.
public
roles() : RolesApi
Return values
RolesApiscimTokens()
Bearer tokens for the SCIM 2.0 provisioning endpoint.
public
scimTokens() : ScimTokensApi
Return values
ScimTokensApiscopes()
Sub-resource granularity, always addressed under their resource.
public
scopes() : ScopesApi
Return values
ScopesApiserviceAccounts()
Machine identities, their secrets, and the certificate a device-bound one authenticates with.
public
serviceAccounts() : ServiceAccountsApi
Return values
ServiceAccountsApisettings()
Effective settings, and the organization/tenant layers they resolve from.
public
settings() : SettingsApi
Return values
SettingsApissoComplete()
`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
SsoCompleteResultssoCompleteHandoff()
`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
SsoCompleteResultssoCompleteOauth2()
`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
SsoCompleteResultssoProviders()
`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
FederationProviderListssoStart()
`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
SsoStartResultssoStartOauth2()
`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
SsoStartResulttenants()
Tenants within an organization -- the isolation boundary every other namespace is scoped to.
public
tenants() : TenantsApi
Return values
TenantsApitokenExchange()
`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
$subjectTokenis. 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
Return values
ExchangedTokenumaChallengeHeader()
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
stringumaDeleteResource()
`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
Return values
RequestingPartyTokenumaListResources()
`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|nullumaReadResource()
`GET /uma2/rreg/resource_set/{id}` (CONTRACT.md §20.1).
public
umaReadResource(Sensitive|string $pat, string $id) : ResourceSet
Parameters
- $pat : Sensitive|string
- $id : string
Return values
ResourceSetumaRegisterResource()
`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
ResourceSetumaRequestTicket()
`POST /uma2/perm` (CONTRACT.md §20.1) — mint a permission ticket.
public
umaRequestTicket(Sensitive|string $pat, array<int, RequestedPermission> $permissions) : Sensitive
Parameters
- $pat : Sensitive|string
- $permissions : array<int, RequestedPermission>
Return values
SensitiveumaUpdateResource()
`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
ResourceSetusers()
Users within the client's tenant, and the administrative side of their second factor and lockout state.
public
users() : UsersApi
Return values
UsersApiverifyEmail()
`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
Return values
VerifiedLogoutTokenverifyMfa()
`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
LoginResultwebauthnAuthenticateFinish()
`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
WebauthnLoginResultwebauthnAuthenticateStart()
`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
WebauthnChallengewebauthnDiscoverableFinish()
`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
WebauthnLoginResultwebauthnDiscoverableStart()
`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
WebauthnChallengewebauthnPolicy()
Per-tenant attestation policy governing the §24 ceremonies, and the compliance report over it.
public
webauthnPolicy() : WebauthnPolicyApi
Return values
WebauthnPolicyApiwebauthnRegisterFinish()
`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
WebauthnCredentialwebauthnRegisterStart()
`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
WebauthnChallengewebhooks()
Outbound event notifications. Delivery signatures are verified with the §13 helper, which this namespace configures.
public
webhooks() : WebhooksApi