OidcClient
in package
The OIDC / SSO relying-party engine (CONTRACT.md §12) behind {@see \Axiam\Sdk\AxiamClient}'s nine public `oidc*`/`introspect`/`revoke`/`sso*` methods.
Per the T1 TypeScript reference's own judgment call 1 ("Host object"): unlike the
TypeScript SDK (whose CI forbids Node-only crypto from leaking into a browser
bundle, forcing a separate OidcClient), THIS SDK has no such constraint — the
canonical §12.2 method names live directly on AxiamClient, exactly
as the plan's T8 item says. This class is the internal engine AxiamClient composes (mirroring how it already composes AuthzDispatcher for §1 authz) — never part of the public API surface
itself.
Everything below is reuse, not reimplementation (§12 forbids forking):
- transport, §4 cookie jar, §5 tenant header, §6 TLS → the
$httpGuzzle client (AxiamClient's OWN$plainHttp— AuthMiddleware only, so a 401 from/oauth2/introspect//oauth2/revokestructurally can never reach RefreshMiddleware and therefore never enters the §9 guard, §12.3 rule 3); - §9 single-flight refresh → Session::refreshGuard();
- §12.4 rules 1–2 (alg/kid/signature) → JwksVerifier::verifyIdTokenSignature(),
the SAME verifier instance the §10 middleware already uses for AXIAM's own access
tokens (AXIAM's OIDC provider and its own auth server are the SAME origin, so
there is exactly one JWKS to trust — no separate per-
jwks_uriverifier map is needed here, unlike a generic multi-issuer RP); - §12.4 rules 3–6 (issuer/audience/time/nonce) → IdTokenValidator;
- §7/§12.5 redaction → Sensitive.
Table of Contents
Constants
- ACCESS_TOKEN_TYPE : mixed = 'urn:ietf:params:oauth:token-type:access_token'
- The `actor_token_type` this SDK sends, and the `subject_token_type` it sends when the caller names none — an AXIAM-issued access token (§15.1).
- BACKCHANNEL_LOGOUT_EVENT : mixed = 'http://schemas.openid.net/event/backchannel-lo...
- The `events` member that distinguishes a logout token from an ID token (OIDC Back-Channel Logout 1.0 §2.4).
- DEFAULT_POLL_INTERVAL_SECONDS : mixed = 5
- Polling interval used when the authorization response omits `interval` (RFC 8628 §3.2, §14.2 rule 2). An SDK MUST NOT hard-code a faster floor.
- DEVICE_CODE_GRANT_TYPE : mixed = 'urn:ietf:params:oauth:grant-type:device_code'
- `grant_type` of the device access-token request (RFC 8628 §3.4).
- DISCOVERY_PATH : mixed = '/.well-known/openid-configuration'
- JWT_TOKEN_TYPE : mixed = 'urn:ietf:params:oauth:token-type:jwt'
- A JWT from a trusted external issuer — the cross-domain exchange of §15.7.
- MAX_LOGOUT_TOKEN_AGE_SECONDS : mixed = 300
- Maximum accepted age for a logout token's `iat`, in seconds. AXIAM issues them with a 120 s lifetime; this bound is the same order and stops a token captured from a mis-configured RP being replayed days later.
- MIN_DISCOVERY_TTL_SECONDS : mixed = 300
- Minimum — and default — discovery-cache TTL, in seconds. CONTRACT.md §12.3 rule 6 sets a floor of 5 minutes; a smaller configured value is raised to it.
- RREG_PATH : mixed = '/uma2/rreg/resource_set'
- FedAuthz §2.2 resource registration endpoint.
- SLOW_DOWN_INCREMENT_SECONDS : mixed = 5
- Seconds added to the polling interval on each `slow_down` (§14.2 rule 1). The increase is permanent and cumulative.
- SSO_CALLBACK_PATH : mixed = '/api/v1/auth/federation/oidc/callback'
- SSO_HANDOFF_PATH : mixed = '/api/v1/auth/federation/handoff'
- Path of the handoff-code redemption endpoint (contract 1.38).
- SSO_OAUTH2_CALLBACK_PATH : mixed = '/api/v1/auth/federation/oauth2/callback'
- Path of the plain-OAuth2 federation step-2 (callback) endpoint (contract 1.38).
- SSO_OAUTH2_START_PATH : mixed = '/api/v1/auth/federation/oauth2/start'
- Path of the plain-OAuth2 federation step-1 endpoint (contract 1.38).
- SSO_PROVIDERS_PATH : mixed = '/api/v1/auth/federation/providers'
- Path of the public provider-listing endpoint (contract 1.38).
- SSO_START_PATH : mixed = '/api/v1/auth/federation/oidc/start'
- TOKEN_EXCHANGE_GRANT_TYPE : mixed = 'urn:ietf:params:oauth:grant-type:token-exchange'
- `grant_type` of an RFC 8693 exchange.
- UMA_CLAIM_TOKEN_FORMAT : mixed = 'urn:ietf:params:oauth:token-type:access_token'
- The only `claim_token_format` AXIAM implements. §20.2 rule 2 makes the `claim_token` itself required rather than defaulted; the *format* has one value, so the SDK supplies it.
- UMA_PROTECTION_SCOPE : mixed = 'uma_protection'
- The scope a PAT must carry (§20.2 rule 1) — for callers minting one.
- UMA_TICKET_GRANT_TYPE : mixed = 'urn:ietf:params:oauth:grant-type:uma-ticket'
- `grant_type` of the UMA 2.0 ticket grant (CONTRACT.md §20.1).
Methods
- __construct() : mixed
- deviceAuthorize() : DeviceAuthorization
- `POST /oauth2/device_authorization` (§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, poll to completion.
- devicePoll() : OidcTokenSet
- `POST /oauth2/token` with the device-code grant (§14.1) — **one** poll attempt.
- introspect() : IntrospectionResult
- `POST /oauth2/introspect` (RFC 7662, §12.1) — ask the server whether a token is active and, if so, for its metadata.
- loginClientCredentials() : OidcTokenSet
- `POST /oauth2/token` with `grant_type=client_credentials` (§12.1) — service-account machine-to-machine login.
- logoutUrl() : string
- Build the RP-initiated logout URL to redirect the user agent to (§12.7.2).
- normalizeOrigin() : string
- Normalize a URL to its cache key: lowercased scheme and host with the port always explicit (§12.3 rule 6). `https://IAM.example.com/` and `https://iam.example.com:443/x` therefore share one key, while `http://iam.example.com` gets its own.
- oidcBegin() : AuthorizationRequest
- Build an authorization request (§12.1) — **pure local computation, no network I/O**.
- oidcDiscover() : OidcConfiguration
- `GET /.well-known/openid-configuration` (§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).
- oidcDiscoverAsync() : PromiseInterface
- Async entry point exercised directly by tests wanting to prove single-flight de-duplication under Guzzle's async interface — mirrors {@see JwksVerifier}'s own `ensureFreshAsync()`/`ensureFresh()` split.
- oidcExchange() : OidcTokenSet
- `POST /oauth2/token` with `grant_type=authorization_code` (§12.1) — exchange an authorization code for a token set, validating the returned ID token in full before returning.
- 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` (§12.1) — refresh an {@see OidcTokenSet}, governed by a §9-conformant single-flight refresh guard.
- revoke() : void
- `POST /oauth2/revoke` (RFC 7009, §12.1) — revoke an access or refresh token.
- ssoComplete() : SsoCompleteResult
- `POST /api/v1/auth/federation/oidc/callback` (§12.1) — step 2 of upstream SSO: consumes the single-use `state`, provisions or links the user, and establishes the session.
- ssoCompleteHandoff() : SsoCompleteResult
- `POST /api/v1/auth/federation/handoff` (§12.1) — redeem the single-use code the SAML and Apple flows deliver.
- ssoCompleteOauth2() : SsoCompleteResult
- `POST /api/v1/auth/federation/oauth2/callback` (§12.1) — step 2 of a plain-OAuth2 login.
- ssoProviders() : FederationProviderList
- `GET /api/v1/auth/federation/providers` (§12.1) — which "Sign in with X" buttons to render for a workspace.
- ssoStart() : SsoStartResult
- `POST /api/v1/auth/federation/oidc/start` (§12.1) — step 1 of first-time SSO against an **upstream** IdP. No JWT required.
- ssoStartOauth2() : SsoStartResult
- `POST /api/v1/auth/federation/oauth2/start` (§12.1) — step 1 of a login through a **plain-OAuth2** upstream (GitHub, Facebook, `generic_oauth2`).
- tokenExchange() : ExchangedToken
- `POST /oauth2/token` with the RFC 8693 grant (§15.1) — exchange a token for a **narrower** one.
- umaChallengeHeader() : string
- Format a `WWW-Authenticate: UMA` header (§20.3, emit half) — pure local computation, for a resource server that has just minted a ticket.
- umaDeleteResource() : void
- `DELETE /uma2/rreg/resource_set/{id}` (§20.1) — deregister a resource set.
- umaExchangeTicket() : RequestingPartyToken
- `POST /oauth2/token` with the UMA ticket grant (§20.1) — redeem a permission ticket for a Requesting Party Token.
- umaListResources() : array<int, string>
- `GET /uma2/rreg/resource_set` (§20.1) — the ids **this client** registered.
- umaParseChallenge() : UmaChallenge|null
- Parse a `WWW-Authenticate: UMA …` header (§20.3) — pure local computation.
- umaReadResource() : ResourceSet
- `GET /uma2/rreg/resource_set/{id}` (§20.1) — read a registered resource set.
- umaRegisterResource() : ResourceSet
- `POST /uma2/rreg/resource_set` (§20.1) — register a resource set.
- umaRequestTicket() : Sensitive
- `POST /uma2/perm` (§20.1) — mint a permission ticket for the pairs a caller lacks.
- umaUpdateResource() : ResourceSet
- `PUT /uma2/rreg/resource_set/{id}` (§20.1) — replace a resource set's state.
- verifyLogoutToken() : VerifiedLogoutToken
- Verify a back-channel logout token the OP POSTed to this application's `backchannel_logout_uri` (§12.7.3).
Constants
ACCESS_TOKEN_TYPE
The `actor_token_type` this SDK sends, and the `subject_token_type` it sends when the caller names none — an AXIAM-issued access token (§15.1).
public
mixed
ACCESS_TOKEN_TYPE
= 'urn:ietf:params:oauth:token-type:access_token'
BACKCHANNEL_LOGOUT_EVENT
The `events` member that distinguishes a logout token from an ID token (OIDC Back-Channel Logout 1.0 §2.4).
public
mixed
BACKCHANNEL_LOGOUT_EVENT
= 'http://schemas.openid.net/event/backchannel-logout'
DEFAULT_POLL_INTERVAL_SECONDS
Polling interval used when the authorization response omits `interval` (RFC 8628 §3.2, §14.2 rule 2). An SDK MUST NOT hard-code a faster floor.
public
mixed
DEFAULT_POLL_INTERVAL_SECONDS
= 5
DEVICE_CODE_GRANT_TYPE
`grant_type` of the device access-token request (RFC 8628 §3.4).
public
mixed
DEVICE_CODE_GRANT_TYPE
= 'urn:ietf:params:oauth:grant-type:device_code'
DISCOVERY_PATH
public
mixed
DISCOVERY_PATH
= '/.well-known/openid-configuration'
JWT_TOKEN_TYPE
A JWT from a trusted external issuer — the cross-domain exchange of §15.7.
public
mixed
JWT_TOKEN_TYPE
= 'urn:ietf:params:oauth:token-type:jwt'
Pass it as tokenExchange's $subjectTokenType to exchange a partner IdP's token.
AXIAM also accepts ACCESS_TOKEN_TYPE for an external issuer, and refuses
refresh and ID token types by name.
MAX_LOGOUT_TOKEN_AGE_SECONDS
Maximum accepted age for a logout token's `iat`, in seconds. AXIAM issues them with a 120 s lifetime; this bound is the same order and stops a token captured from a mis-configured RP being replayed days later.
public
mixed
MAX_LOGOUT_TOKEN_AGE_SECONDS
= 300
MIN_DISCOVERY_TTL_SECONDS
Minimum — and default — discovery-cache TTL, in seconds. CONTRACT.md §12.3 rule 6 sets a floor of 5 minutes; a smaller configured value is raised to it.
public
mixed
MIN_DISCOVERY_TTL_SECONDS
= 300
RREG_PATH
FedAuthz §2.2 resource registration endpoint.
public
mixed
RREG_PATH
= '/uma2/rreg/resource_set'
SLOW_DOWN_INCREMENT_SECONDS
Seconds added to the polling interval on each `slow_down` (§14.2 rule 1). The increase is permanent and cumulative.
public
mixed
SLOW_DOWN_INCREMENT_SECONDS
= 5
SSO_CALLBACK_PATH
public
mixed
SSO_CALLBACK_PATH
= '/api/v1/auth/federation/oidc/callback'
SSO_HANDOFF_PATH
Path of the handoff-code redemption endpoint (contract 1.38).
public
mixed
SSO_HANDOFF_PATH
= '/api/v1/auth/federation/handoff'
SSO_OAUTH2_CALLBACK_PATH
Path of the plain-OAuth2 federation step-2 (callback) endpoint (contract 1.38).
public
mixed
SSO_OAUTH2_CALLBACK_PATH
= '/api/v1/auth/federation/oauth2/callback'
SSO_OAUTH2_START_PATH
Path of the plain-OAuth2 federation step-1 endpoint (contract 1.38).
public
mixed
SSO_OAUTH2_START_PATH
= '/api/v1/auth/federation/oauth2/start'
SSO_PROVIDERS_PATH
Path of the public provider-listing endpoint (contract 1.38).
public
mixed
SSO_PROVIDERS_PATH
= '/api/v1/auth/federation/providers'
SSO_START_PATH
public
mixed
SSO_START_PATH
= '/api/v1/auth/federation/oidc/start'
TOKEN_EXCHANGE_GRANT_TYPE
`grant_type` of an RFC 8693 exchange.
public
mixed
TOKEN_EXCHANGE_GRANT_TYPE
= 'urn:ietf:params:oauth:grant-type:token-exchange'
UMA_CLAIM_TOKEN_FORMAT
The only `claim_token_format` AXIAM implements. §20.2 rule 2 makes the `claim_token` itself required rather than defaulted; the *format* has one value, so the SDK supplies it.
public
mixed
UMA_CLAIM_TOKEN_FORMAT
= 'urn:ietf:params:oauth:token-type:access_token'
UMA_PROTECTION_SCOPE
The scope a PAT must carry (§20.2 rule 1) — for callers minting one.
public
mixed
UMA_PROTECTION_SCOPE
= 'uma_protection'
UMA_TICKET_GRANT_TYPE
`grant_type` of the UMA 2.0 ticket grant (CONTRACT.md §20.1).
public
mixed
UMA_TICKET_GRANT_TYPE
= 'urn:ietf:params:oauth:grant-type:uma-ticket'
Methods
__construct()
public
__construct(Client $http, string $baseUrl, Session $session, JwksVerifier $jwksVerifier[, string|null $clientId = null ][, Sensitive|null $clientSecret = null ][, string|null $tenantId = null ][, string|null $orgId = null ][, string|null $orgSlug = null ][, string|null $tenantSlugForSso = null ][, int $discoveryTtlSeconds = self::MIN_DISCOVERY_TTL_SECONDS ][, int $clockSkewSec = IdTokenValidator::MAX_CLOCK_SKEW_SEC ]) : mixed
Parameters
- $http : Client
-
AuthMiddleware ONLY — never RefreshMiddleware (§12.3 rule 3/rule 4).
- $baseUrl : string
-
The AXIAM server base URL this engine's client is bound to.
- $session : Session
-
The shared session (cookie jar, CSRF, §9 refresh guard).
- $jwksVerifier : JwksVerifier
-
The SAME verifier the §10 middleware uses for AXIAM's own access tokens.
- $clientId : string|null = null
-
The relying party's OAuth2
client_id. - $clientSecret : Sensitive|null = null
-
The confidential client's
client_secret, already Sensitive-wrapped. - $tenantId : string|null = null
-
Client-level
tenant_idUUID fallback (§12.3 rule 4). - $orgId : string|null = null
-
Client-level organization UUID fallback for
ssoStart(§5.1). - $orgSlug : string|null = null
-
Client-level organization slug fallback for
ssoStart(§5.1). - $tenantSlugForSso : string|null = null
-
Client-level tenant slug fallback for
ssoStart(§5.1). - $discoveryTtlSeconds : int = self::MIN_DISCOVERY_TTL_SECONDS
-
Discovery-cache TTL, floored to self::MIN_DISCOVERY_TTL_SECONDS.
- $clockSkewSec : int = IdTokenValidator::MAX_CLOCK_SKEW_SEC
-
Permitted ID-token clock skew, clamped to IdTokenValidator::MAX_CLOCK_SKEW_SEC.
deviceAuthorize()
`POST /oauth2/device_authorization` (§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 (§14.1).
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, 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
$onUserCode is called with the DeviceAuthorization before the first
poll — §14.3 rule 2 requires the caller to have had the chance to display the
code before polling begins. The SDK never prints it: what the device does with it
(screen, QR code, e-ink panel) is the application's decision.
Per §14.3 rule 4 (contract 1.7 errata) the token set is returned; whether it is
adopted is $adoptAsCredential, the same opt-in flag
loginClientCredentials uses in this SDK.
Polling follows §14.2: the interval comes from the response; slow_down adds 5 s
permanently; authorization_pending loops; access_denied and expired_token
raise distinct errors; polling stops at expiresIn even if the server has not yet
said expired_token. A 5xx or transport failure mid-poll is not terminal
(rule 6) — a server restart must not lose a grant the user has already approved.
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, so tests can assert the §14.2 arithmetic without spending it in wall-clock time. Defaults to
sleep().
Return values
OidcTokenSetdevicePoll()
`POST /oauth2/token` with the device-code grant (§14.1) — **one** poll attempt.
public
devicePoll(Sensitive|string $deviceCode[, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : OidcTokenSet
The raw single call, so an application driving its own loop (a UI rendering a
countdown, say) can. All five RFC 8628 §3.5 answers surface as
OAuthProtocolError — authorization_pending and
slow_down included — so a hand-rolled loop sees exactly what deviceLogin
sees. Most callers want deviceLogin.
Parameters
- $deviceCode : Sensitive|string
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
Return values
OidcTokenSetintrospect()
`POST /oauth2/introspect` (RFC 7662, §12.1) — ask the server whether a token is active and, if so, for its metadata.
public
introspect(Sensitive|string $token[, string|null $tokenTypeHint = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : IntrospectionResult
Requires confidential-client credentials (§12.1 note 4). A 401 here is a
client-credential failure surfaced as OAuthProtocolError;
it never enters the §9 refresh guard — this method's $http transport carries
NO RefreshMiddleware at all, so there is structurally no guard for it to enter
(§12.3 rule 3).
Parameters
- $token : Sensitive|string
- $tokenTypeHint : string|null = null
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
Tags
Return values
IntrospectionResultloginClientCredentials()
`POST /oauth2/token` with `grant_type=client_credentials` (§12.1) — service-account machine-to-machine login.
public
loginClientCredentials([string|null $scope = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ][, bool $adoptAsCredential = false ]) : OidcTokenSet
Requests no openid scope, so the response carries no id_token. Pass
$adoptAsCredential: true to additionally use the returned access token as this
session's bearer credential for subsequent REST calls (§12.1, a MAY).
Parameters
- $scope : string|null = null
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
- $adoptAsCredential : bool = false
Tags
Return values
OidcTokenSetlogoutUrl()
Build the RP-initiated logout URL to redirect the user agent to (§12.7.2).
public
logoutUrl(Sensitive|string $idToken[, string|null $postLogoutRedirectUri = null ][, string|null $state = null ][, OidcConfiguration|null $configuration = null ]) : string
Performs no network I/O beyond the discovery fetch the SDK caches anyway, and does not clear this client's own session: whether the local session ends is the application's decision — a backend holding a service-account session must not lose it because a user logged out.
end_session_endpoint is read from discovery and never synthesised from the issuer
(rule 1). $postLogoutRedirectUri is passed through unvalidated against any local
list (rule 3): the allow-list lives in the client's server-side registration, and
a client-side copy would drift and reject a URI an operator had just registered.
Parameters
- $idToken : Sensitive|string
- $postLogoutRedirectUri : string|null = null
- $state : string|null = null
- $configuration : OidcConfiguration|null = null
Tags
Return values
stringnormalizeOrigin()
Normalize a URL to its cache key: lowercased scheme and host with the port always explicit (§12.3 rule 6). `https://IAM.example.com/` and `https://iam.example.com:443/x` therefore share one key, while `http://iam.example.com` gets its own.
public
static normalizeOrigin(string $url) : string
Parameters
- $url : string
Return values
stringoidcBegin()
Build an authorization request (§12.1) — **pure local computation, no network I/O**.
public
oidcBegin(OidcConfiguration $configuration, string $redirectUri[, string|array<int, string> $scope = null ][, array<string, string> $extraParams = [] ]) : AuthorizationRequest
Generates a 32-byte CSPRNG state and nonce (base64url, unpadded) and a fresh
PKCE verifier/challenge pair using S256 only — plain is not implemented
anywhere in this SDK. The URL is built from the discovery document's
authorization_endpoint with exactly the eight parameters §12.1 rule 5 mandates,
plus any $extraParams the caller adds.
Nothing is stored: persist the returned state, nonce and codeVerifier
yourself (§12.3 rule 1).
Parameters
- $configuration : OidcConfiguration
- $redirectUri : string
- $scope : string|array<int, string> = null
- $extraParams : array<string, string> = []
Tags
Return values
AuthorizationRequestoidcDiscover()
`GET /.well-known/openid-configuration` (§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).
public
oidcDiscover() : OidcConfiguration
Return values
OidcConfigurationoidcDiscoverAsync()
Async entry point exercised directly by tests wanting to prove single-flight de-duplication under Guzzle's async interface — mirrors {@see JwksVerifier}'s own `ensureFreshAsync()`/`ensureFresh()` split.
public
oidcDiscoverAsync() : PromiseInterface
Return values
PromiseInterfaceoidcExchange()
`POST /oauth2/token` with `grant_type=authorization_code` (§12.1) — exchange an authorization code for a token set, validating the returned ID token in full before returning.
public
oidcExchange(string $code, Sensitive|string $codeVerifier, string $redirectUri, string $nonce[, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : OidcTokenSet
The $nonce argument is mandatory: this grant always requests the openid
scope, so §12.4 rule 6 always applies. If any §12.4 rule fails, the whole
token set is discarded and AuthError is raised with the matching reason
code — the access and refresh tokens from the same response are never returned
(§12.4 rule 7).
Parameters
- $code : string
- $codeVerifier : Sensitive|string
- $redirectUri : string
- $nonce : string
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
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 over an authenticated channel and puts an
opaque request_uri in the redirect. What travels through the browser is then a
random string that cannot be edited into meaning something else.
Required for a FAPI 2.0 client: profile: "fapi2" refuses a registration that
does not set require_par, so such a client cannot authorize any other way (§21.1).
Not retried on a 5xx or a transport failure — it is a POST that creates server
state, so it falls outside §16.2's read-only eligibility exactly as
self::oidcExchange() does. The safe recovery is a fresh push, which costs one
round trip and cannot double-consume anything (§26.2 rule 4).
Parameters
- $request : AuthorizationRequest
- $redirectUri : string
- $configuration : OidcConfiguration|null = null
- $scope : string|array<int, string>|null = null
- $tenantId : string|null = null
Tags
Return values
PushedAuthorizationRequestoidcRefresh()
`POST /oauth2/token` with `grant_type=refresh_token` (§12.1) — refresh an {@see OidcTokenSet}, governed by a §9-conformant single-flight refresh guard.
public
oidcRefresh(Sensitive|string $refreshToken[, string|null $scope = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : OidcTokenSet
This is a distinct operation from AxiamClient::refresh(), which drives the
cookie/opaque-token session path at POST /api/v1/auth/refresh. The two are
never merged or aliased and neither falls back to the other (§12.1). They DO
share Session's single §9 guard slot, tagged with
Session::REFRESH_KIND_OIDC, so at most one refresh of either kind is ever
in flight for a session:
- If the guard is busy with ANOTHER
oidcRefreshcall (same kind), this call does not issue its own wire call — it awaits and reuses that one leader's outcome (CONTRACT.md §9 rule 2, F-06). Refresh tokens are single-use with rotation, so a second wire call here would replay an already-consumed token and failinvalid_grant; sharing the result is the whole point of the guard. - If the guard is busy with a cookie-session refresh (different kind, which
cannot produce an
OidcTokenSet), this call retries (bounded, 3 attempts) once that refresh settles, rather than returning a stale/foreign result.
An id_token in the response is validated against §12.4 rules 1–5 and 7; rule 6
(nonce) is skipped, since OIDC Core §12.2 does not require a nonce in a
refresh-issued ID token.
Parameters
- $refreshToken : Sensitive|string
- $scope : string|null = null
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
Return values
OidcTokenSetrevoke()
`POST /oauth2/revoke` (RFC 7009, §12.1) — revoke an access or refresh token.
public
revoke(Sensitive|string $token[, string|null $tokenTypeHint = null ][, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : void
Returns nothing.
Per RFC 7009 the server answers 200 for unknown, expired and already-revoked
tokens alike, so revocation is idempotent: any 200 is success and no error
is raised for a token the server has never seen. Only a 401 (client
authentication failed) is an error, surfaced as
OAuthProtocolError (§12.1 note 5, §12.3 rule 3). A 5xx
stays a NetworkError — it never becomes "success" just because RFC 7009
treats an unknown token as success.
Parameters
- $token : Sensitive|string
- $tokenTypeHint : string|null = null
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
Tags
ssoComplete()
`POST /api/v1/auth/federation/oidc/callback` (§12.1) — step 2 of upstream SSO: consumes the single-use `state`, provisions or links the user, and establishes the session.
public
ssoComplete(string $state, string $code) : SsoCompleteResult
The session arrives as Set-Cookie, not in the response body (§12.1 note 6)
— $this->http shares AxiamClient's §4 cookie jar, so the
session is captured automatically. On success the §3 CSRF token freshly set by
the server is captured too, exactly as login() does.
§12.4 does not apply here — no ID token ever reaches the SDK on the federation path.
Parameters
- $state : string
- $code : string
Return values
SsoCompleteResultssoCompleteHandoff()
`POST /api/v1/auth/federation/handoff` (§12.1) — redeem the single-use code the SAML and Apple flows deliver.
public
ssoCompleteHandoff(string $code) : SsoCompleteResult
Those two protocols return cross-site, so the server cannot set
SameSite=Strict session cookies on that response. It instead redirects the
browser to the SPA's callback URL with a
FederationProviderList::HANDOFF_QUERY_PARAM query parameter; this call
posts that code back same-origin, and this response is the one that carries
the cookies (§12.1 note 12).
The code is gone either way. It is valid for
FederationProviderList::HANDOFF_CODE_TTL_SECONDS seconds 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. Unknown, expired and
already-redeemed all answer the same 401, deliberately.
Parameters
- $code : string
Return values
SsoCompleteResultssoCompleteOauth2()
`POST /api/v1/auth/federation/oauth2/callback` (§12.1) — step 2 of a plain-OAuth2 login.
public
ssoCompleteOauth2(string $state, string $code) : SsoCompleteResult
The session arrives as Set-Cookie (§12.1 note 6) through the same §4
cookie jar self::ssoComplete() uses, and the §3 CSRF token is captured
the same way.
§12.4 does not apply: an OAuth2 provider issues no ID token, so there is
nothing to validate — the server authenticated the user by calling a
configured userinfo endpoint with the access token it had just received
(§12.1 note 11).
Parameters
- $state : string
- $code : string
Return values
SsoCompleteResultssoProviders()
`GET /api/v1/auth/federation/providers` (§12.1) — which "Sign in with X" buttons to render for a workspace.
public
ssoProviders([string|null $orgId = null ][, string|null $orgSlug = null ][, string|null $tenantId = null ][, string|null $tenantSlug = null ]) : FederationProviderList
The identifiers travel as query parameters; this is a GET and sends no
body. The neighbouring start operations take the same four in a JSON body,
and the two are one copy-paste apart.
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 providers array (§12.1 note 9). Every one of them comes
back as an ordinary result and nothing here synthesises a not-found: the
endpoint is deliberately shaped so it cannot be used to enumerate
organization or tenant slugs, and an SDK that reintroduced the distinction
would reintroduce the oracle. A caller learns it named the workspace wrongly
at the start operations, where every failure is a uniform 401.
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` (§12.1) — step 1 of first-time SSO against an **upstream** IdP. No JWT required.
public
ssoStart(string $federationConfigId, string $redirectUri[, string|null $tenantId = null ][, string|null $tenantSlug = null ][, string|null $orgId = null ][, string|null $orgSlug = null ]) : SsoStartResult
One tenant form ($tenantId or $tenantSlug) and one org form ($orgId or
$orgSlug) must be resolvable, from the arguments or from the client's
construction options (§5.1). Redirect the browser to the returned
authorizeUrl and round-trip state back into self::ssoComplete()
unmodified — the server keeps the nonce to itself (§12.1 note 7).
Parameters
- $federationConfigId : string
- $redirectUri : string
- $tenantId : string|null = null
- $tenantSlug : string|null = null
- $orgId : string|null = null
- $orgSlug : string|null = null
Tags
Return values
SsoStartResultssoStartOauth2()
`POST /api/v1/auth/federation/oauth2/start` (§12.1) — step 1 of a login through a **plain-OAuth2** upstream (GitHub, Facebook, `generic_oauth2`).
public
ssoStartOauth2(string $federationConfigId, string $redirectUri[, string|null $tenantId = null ][, string|null $tenantSlug = null ][, string|null $orgId = null ][, string|null $orgSlug = null ]) : SsoStartResult
Call this, rather than self::ssoStart(), exactly when the provider's
protocol is FederationProvider::PROTOCOL_OAUTH2 (§12.1 note 10). The
server refuses a mismatch with 400 rather than accepting it silently, so a
client that assumes OIDC fails on every GitHub button.
PKCE is mandatory on this path and is generated and stored server-side; nothing about it appears in the request or the response (§12.1 note 11).
A 400 here can mean the $redirectUri is not on an origin the deployment
accepts (§12.1 rule 12a). §2's 400 row makes that a NetworkError —
this taxonomy's configuration/programming-error member, as distinct from the
AuthError a 401 gets. 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
Tags
Return values
SsoStartResulttokenExchange()
`POST /oauth2/token` with the RFC 8693 grant (§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
The exchanging client authenticates (client_secret_post): unlike §14's device,
this is a confidential service, so a client with no secret fails here client-side,
with no wire call.
What this method deliberately does not do:
- No default
$actorToken(§15.2 rule 1). Passingnullasks for impersonation; the SDK will not quietly reuse the client's own session token as the actor and turn that into a delegation. - No retry or downgrade on
unauthorized_client(rule 2) — a registration fact an operator must fix. - No auto-narrowing on
invalid_scope(rule 3). The server refuses instead of silently narrowing precisely so the caller finds out here. - No adoption (rule 5), and no flag to enable it — a MUST NOT, where loginClientCredentials adoption is a MAY.
A cross-tenant subject token answers invalid_grant, identically to an expired
one. The SDK does not try to tell them apart (§15.3): the server collapses them
because distinguishing them is a tenant-enumeration signal.
Parameters
- $subjectToken : Sensitive|string
- $subjectTokenType : string
-
What kind of token
$subjectTokenis. Required (§15.1), with no default — a default would be this SDK choosing which kind of credential you hold, which is what §15.7 forbids. Pass ACCESS_TOKEN_TYPE for the same-domain exchange, or JWT_TOKEN_TYPE for a trusted external issuer's JWT (§15.7). It sits second, matching §15.1's canonical order: it was last while it was optional, to spare positional callers, and making it required breaks them anyway — so it may as well be where the contract puts it. The SDK never reads$subjectTokento decide it: which kind of token you hold is something only you know, AXIAM refuses refresh and ID token types by name, and the SDK will not retry a refusal as a different type. - $actorToken : Sensitive|string|null = null
- $scopes : array<int, string>|null = null
-
Omitted from the body when
nullor empty. - $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 (§20.3, emit half) — pure local computation, for a resource server that has just minted a ticket.
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}` (§20.1) — deregister a resource set.
public
umaDeleteResource(Sensitive|string $pat, string $id) : void
Parameters
- $pat : Sensitive|string
- $id : string
umaExchangeTicket()
`POST /oauth2/token` with the UMA ticket grant (§20.1) — redeem a permission ticket for a Requesting Party Token.
public
umaExchangeTicket(Sensitive|string $ticket, Sensitive|string $claimToken[, string|null $tenantId = null ][, OidcConfiguration|null $configuration = null ]) : RequestingPartyToken
Unlike the Protection API above, this is a token-endpoint grant: the client
authenticates through the form body (client_secret_post), so a client with no
secret fails here client-side with no wire call.
What this method deliberately does not do:
- No retry, ever (§20.2 rule 6) — not on
5xx, not on a timeout, not oninvalid_grant. This is the one documented exception to §16, and it is a security rule rather than a performance one: the ticket is consumed before the request is evaluated, so a failed exchange has already spent it, and a retry is a second redemption — exactly the concurrent redemption a server whose storage engine this SDK cannot attest may admit twice (ilpanich/axiam#302). This SDK gets that for free: no/oauth2/*call passes through RetryPolicy, and the transport this class holds carries no RefreshMiddleware. - No defaulted
$claimToken(rule 2). It is the only channel that names the requesting party; defaulting it to the resource server's own PAT would mint an RPT for the resource server rather than for the user. - No auto-narrowing on
access_denied(rule 3). A partial grant is refused whole, and whether two-of-three permissions is useful is the application's judgment, not this SDK's. - No adoption (rule 4). The RPT is the requesting party's token; adopting it would re-privilege every later call this resource server makes as that user.
- No refresh token (rule 5) — the grant issues none, and RequestingPartyToken has nowhere to put one. Re-run the grant with a new ticket to get a fresh RPT.
The four ticket refusals — unknown, expired, already used, minted by another
client — all arrive as one invalid_grant, and this SDK does not guess which
(§20.4): the server collapses them because telling them apart lets a caller probe
for live ticket handles.
Parameters
- $ticket : Sensitive|string
- $claimToken : Sensitive|string
- $tenantId : string|null = null
- $configuration : OidcConfiguration|null = null
Tags
Return values
RequestingPartyTokenumaListResources()
`GET /uma2/rreg/resource_set` (§20.1) — the ids **this client** registered.
public
umaListResources(Sensitive|string $pat) : array<int, string>
Not the tenant's resource tree: the server scopes the listing to the registering client, so a PAT is not an enumeration handle.
Parameters
- $pat : Sensitive|string
Return values
array<int, string>umaParseChallenge()
Parse a `WWW-Authenticate: UMA …` header (§20.3) — pure local computation.
public
umaParseChallenge(string $header) : UmaChallenge|null
Performs no exchange, deliberately: the as_uri names an authorization server
this client has not necessarily chosen to trust. See UmaChallenge::parse().
Parameters
- $header : string
Return values
UmaChallenge|nullumaReadResource()
`GET /uma2/rreg/resource_set/{id}` (§20.1) — read a registered resource set.
public
umaReadResource(Sensitive|string $pat, string $id) : ResourceSet
Parameters
- $pat : Sensitive|string
- $id : string
Return values
ResourceSetumaRegisterResource()
`POST /uma2/rreg/resource_set` (§20.1) — register a 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, directly usable as the
$resourceId of a later umaRequestTicket call and anywhere else this SDK
takes a resource id.
Parameters
- $pat : Sensitive|string
-
The Protection API Token — a client-credentials token carrying the
uma_protectionscope (§20.2 rule 1). This SDK never falls back to the session's own token when one is not passed. - $name : string
- $type : string|null = null
- $resourceScopes : array<int, string> = []
Return values
ResourceSetumaRequestTicket()
`POST /uma2/perm` (§20.1) — mint a permission ticket for the pairs a caller lacks.
public
umaRequestTicket(Sensitive|string $pat, array<int, RequestedPermission> $permissions) : Sensitive
The ticket comes back wrapped: for its 60-second life it is the credential that converts into an RPT, and a short lifetime is not the same as a harmless one (§20.6).
Parameters
- $pat : Sensitive|string
- $permissions : array<int, RequestedPermission>
Return values
SensitiveumaUpdateResource()
`PUT /uma2/rreg/resource_set/{id}` (§20.1) — replace a resource set's state.
public
umaUpdateResource(Sensitive|string $pat, string $id, string $name[, string|null $type = null ][, array<int, string> $resourceScopes = [] ]) : ResourceSet
$resourceScopes replaces the declared list; it does not merge with it
(§20.2 rule 8). This method deliberately performs no read-modify-write: folding
the current scopes into the payload as a convenience would make removing a scope
impossible through this SDK.
Parameters
- $pat : Sensitive|string
- $id : string
- $name : string
- $type : string|null = null
- $resourceScopes : array<int, string> = []
Return values
ResourceSetverifyLogoutToken()
Verify a back-channel logout token the OP POSTed to this application's `backchannel_logout_uri` (§12.7.3).
public
verifyLogoutToken(string $logoutToken[, OidcConfiguration|null $configuration = null ]) : VerifiedLogoutToken
Every check exists because skipping it has a name:
- Signature, through the same §12.4 JWKS verifier the ID-token path uses — no
second key-fetching path — which already pins EdDSA and requires a
kid. iss/aud: a token minted for another RP is not accepted here.eventscarries the back-channel-logout key. This is what distinguishes a logout token from an ID token; skipping it means accepting a replayed ID token as a logout instruction.nonceis absent. Back-Channel Logout 1.0 §2.4 forbids it, and its presence is the documented signature of an ID token being replayed. Rejected, not ignored.- At least one of
sid/sub— a token naming neither identifies nothing. expin the future,iatrecent.
Parameters
- $logoutToken : string
- $configuration : OidcConfiguration|null = null
Tags
Return values
VerifiedLogoutToken —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, and an SDK-side guard would have no durable
store and would silently drop a real second logout after a restart.