SsfReceiver
in package
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
issexactly. - $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…); itsjwks_uriis used, and itsissuermust equal$issuer. - $accessTokenProvider : callable(): Array|null = null
-
The bearer
poll()presents: a client-credentials access token carryingssf.manage(for example$client->loginClientCredentials('ssf.manage')->accessToken). Called once per poll;nullfor a push-only receiver. - $replayWindowSeconds : int = self::MIN_REPLAY_WINDOW_SECONDS
-
How long a
jtiis 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
__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
Return values
SsfPollResultverifySet()
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
- three base64url parts, a JSON-object header and payload [
malformed]; typsecevent+jwtorapplication/secevent+jwt, any case [invalid_type];algexactlyEdDSA—none,HS*and every other value refused [invalid_key];- the
kidin the configured JWKS — on a miss, ONE refetch, no more often than once a minute [invalid_key]; - the Ed25519 signature [
invalid_key]; issequal to the configured issuer [invalid_issuer];audequal to, or an array containing, the audience [invalid_audience];- no
exp, nosub; a non-empty stringjti, a numericiat, an objectsub_id;eventsan object with exactly one member [invalid_request]; - a
jtinot 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