AXIAM PHP SDK

SsfReceiver
in package

FinalYes

The SSF receiver helper — CONTRACT.md §32.7 (contract 1.56).

AXIAM is a Shared Signals Framework transmitter: it sends CAEP and RISC security events as Security Event Tokens (RFC 8417) to the relying parties a tenant administrator registered (the §27 ssf namespace). This class is for the relying party that receives them, a different audience from that namespace:

  • self::verifySet() verifies one compact SET — pushed to your endpoint (RFC 8935) or returned by a poll — in the contract's fixed order, and refuses at the first failure with a SetVerificationError naming the step.
  • self::poll() calls the stream's poll endpoint (RFC 8936), verifies every returned SET and hands back the verified and the refused apart.

Neither transmits, signs or registers anything, and neither trusts a key it did not fetch from the configured JWKS: a jwk or x5c header member is never honoured (§32.9).

Build one with AxiamClient::ssfReceiver(), which supplies the client's session-free transport (its §6 TLS policy, no cookies, no session token, no redirects) and base URL.

A push endpoint answers a refusal with 400 and SetFailureReason::pushErrorCode():

try {
    $event = $receiver->verifySet($requestBody);
    // ... act on $event, then answer 202
} catch (SetVerificationError $e) {
    // answer 400 {"err": $e->failureReason->pushErrorCode()}
}

Table of Contents

Constants

FORCED_REFETCH_INTERVAL_SECONDS  : mixed = 60
A forced JWKS refetch (an unknown `kid`) happens at most once per this many seconds, and after a fetch that failed — a fill and an expiry refresh included — no fetch is made for this long (CONTRACT.md §34.2 P6).
JWKS_TTL_SECONDS  : mixed = 300
The cached JWKS expires this long after the successful fetch that filled it, and the next SET fetches again (§34.2 P6: no later than ten minutes).
MIN_REPLAY_WINDOW_SECONDS  : mixed = 7 * 24 * 60 * 60
The replay window's floor and default: seven days, the transmitter's buffer retention (§32.6). A shorter window would forget a `jti` the transmitter can still re-send.

Methods

__construct()  : mixed
__debugInfo()  : array<string, mixed>
What `print_r()` / `var_dump()` show: the configuration, never the access-token provider (a closure that typically captures a bearer) nor the replay store.
poll()  : SsfPollResult
Poll the stream's RFC 8936 endpoint, `{root}/ssf/v1/poll/{stream_id}`, with a bearer from the configured access-token provider, and verify every SET it returns.
verifySet()  : SecurityEvent
Verify one compact SET (CONTRACT.md §32.7), in this order, refusing at the first failure with a {@see SetVerificationError} whose {@see SetFailureReason} is in brackets:

Constants

FORCED_REFETCH_INTERVAL_SECONDS

A forced JWKS refetch (an unknown `kid`) happens at most once per this many seconds, and after a fetch that failed — a fill and an expiry refresh included — no fetch is made for this long (CONTRACT.md §34.2 P6).

public mixed FORCED_REFETCH_INTERVAL_SECONDS = 60

JWKS_TTL_SECONDS

The cached JWKS expires this long after the successful fetch that filled it, and the next SET fetches again (§34.2 P6: no later than ten minutes).

public mixed JWKS_TTL_SECONDS = 300

MIN_REPLAY_WINDOW_SECONDS

The replay window's floor and default: seven days, the transmitter's buffer retention (§32.6). A shorter window would forget a `jti` the transmitter can still re-send.

public mixed MIN_REPLAY_WINDOW_SECONDS = 7 * 24 * 60 * 60

Methods

__construct()

public __construct(ClientInterface $http, string $baseUrl, string $issuer, string $audience[, string|null $jwksUri = null ][, string|null $discoveryUrl = null ][, callable(): Array|null $accessTokenProvider = null ][, int $replayWindowSeconds = self::MIN_REPLAY_WINDOW_SECONDS ][, ReplayStore|null $replayStore = null ][, bool $retryEnabled = true ][, TelemetryDispatcher|null $telemetry = null ][, callable(): int|null $clock = null ]) : mixed
Parameters
$http : ClientInterface

The transport: a session-free client with the SDK's TLS policy — AxiamClient::ssfReceiver() passes its own.

$baseUrl : string

The transmitter root poll() calls ({root}/ssf/v1/poll/{id}).

$issuer : string

The transmitter's issuer, compared with iss exactly.

$audience : string

This receiver's audience — the stream's audience.

$jwksUri : string|null = null

Where the signing keys are (AXIAM: {issuer}/oauth2/jwks). Exactly one of this and $discoveryUrl.

$discoveryUrl : string|null = null

The transmitter's SSF configuration document (/.well-known/ssf-configuration…); its jwks_uri is used, and its issuer must equal $issuer.

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

The bearer poll() presents: a client-credentials access token carrying ssf.manage (for example $client->loginClientCredentials('ssf.manage')->accessToken). Called once per poll; null for a push-only receiver.

$replayWindowSeconds : int = self::MIN_REPLAY_WINDOW_SECONDS

How long a jti is remembered — at least, and by default, self::MIN_REPLAY_WINDOW_SECONDS.

$replayStore : ReplayStore|null = null

Where accepted jtis are kept; an InMemoryReplayStore when omitted.

$retryEnabled : bool = true

§16.1's switch, for poll().

$telemetry : TelemetryDispatcher|null = null

§19, notified before a retry wait and when a poll() returns leaving SETs unjudged (SsfUnjudgedEvent).

$clock : callable(): int|null = null

The current time in seconds (for the refetch limit); time() when omitted.

Tags
throws
ValidationError

locally, when the replay window is below seven days, the issuer or audience is empty, or not exactly one key source is given.

__debugInfo()

What `print_r()` / `var_dump()` show: the configuration, never the access-token provider (a closure that typically captures a bearer) nor the replay store.

public __debugInfo() : array<string, mixed>
Return values
array<string, mixed>

poll()

Poll the stream's RFC 8936 endpoint, `{root}/ssf/v1/poll/{stream_id}`, with a bearer from the configured access-token provider, and verify every SET it returns.

public poll(string $streamId[, SsfPollOptions|null $options = null ]) : SsfPollResult

The body carries exactly the options set — } when none is — so ack and setErrs go out exactly as given. Nothing is acknowledged on your behalf: on the next call, acknowledge the jtis you processed and pass each refused one in setErrs (SetErr::fromReason()) — except a replayed one, which you acknowledge in ack and never report in setErrs (CONTRACT.md §34.2 P2): this receiver accepted that SET earlier, so an error report would tell the transmitter that an event you took had failed, when it removes the event from its queue either way. A SET you neither acknowledge nor refuse is re-offered, and — having been recorded when it verified — then reads as replayed; the receiver records a jti when it accepts the SET, so processing before the next poll (or a persistent ReplayStore) is yours.

The request carries nothing of the SDK's session and follows no redirect. Retried per §16 on a transport failure, a 5xx, 408 or 429 — never on another 4xx, which maps like a management answer (400 ValidationError, 404 NotFoundError, 401 AuthError). A SET that is not a string is refused malformed, one whose verified jti differs from its map key invalid_request.

poll never keeps a jti it does not return (§34.2 P1). A failure that is not a verdict on a SET — a JWKS or configuration fetch that fails, a replay store that cannot answer — refuses nothing: that SET is left unjudged, unrecorded, and listed in SsfPollResult::$unjudged with the failure in SsfPollResult::$unjudgedCause. The replay store is asked nothing more for the batch: every later SET is still checked (steps 1 – 8) and refused if it fails, and is otherwise unjudged too. Neither acknowledge the unjudged SETs nor report them in setErrs: the transmitter offers them again. When no SET of the batch had been accepted yet, nothing was recorded and the failure is raised instead. A poll that returns leaving SETs unjudged emits §19's ssf_unjudged (SsfUnjudgedEvent) with their number and the category, key_fetch or replay_store.

Parameters
$streamId : string
$options : SsfPollOptions|null = null
Tags
throws
AuthError

locally, when no access-token provider was configured.

NetworkError

a key fetch failed before any SET of the batch was accepted (or the poll itself failed); the replay store's own exception likewise.

Return values
SsfPollResult

verifySet()

Verify one compact SET (CONTRACT.md §32.7), in this order, refusing at the first failure with a {@see SetVerificationError} whose {@see SetFailureReason} is in brackets:

public verifySet(string $set) : SecurityEvent
  1. three base64url parts, a JSON-object header and payload [malformed];
  2. typ secevent+jwt or application/secevent+jwt, any case [invalid_type];
  3. alg exactly EdDSA — none, HS* and every other value refused [invalid_key];
  4. the kid in the configured JWKS — on a miss, ONE refetch, no more often than once a minute [invalid_key];
  5. the Ed25519 signature [invalid_key];
  6. iss equal to the configured issuer [invalid_issuer];
  7. aud equal to, or an array containing, the audience [invalid_audience];
  8. no exp, no sub; a non-empty string jti, a numeric iat, an object sub_id; events an object with exactly one member [invalid_request];
  9. a jti not seen within the replay window [replayed] — recorded only once 1–8 passed.

A SET that verifies has been recorded: verifying it again is replayed. Acknowledge a polled SET once you have processed it, or a re-offer reads as a replay.

Parameters
$set : string
Tags
throws
SetVerificationError

the SET is refused.

NetworkError

the JWKS (or the configuration document) could not be fetched, a fetch failed less than a minute ago so none is made (§34.2 P6), or the replay store could not answer — none of which is a verdict on the SET. A store's own exception is chained as the cause; an SDK error it raised passes through unchanged (CONTRACT.md §2, §34.2 P3).

AxiamException

ext-sodium is not loaded (composer.json requires it) — no verdict on the SET either.

Return values
SecurityEvent
On this page

Search results