Quarkus OpenID SSF
A Quarkus extension family for the OpenID Shared Signals Framework (SSF) and its companion specs (CAEP, RISC, RFC 8417 Security Event Tokens, RFC 8935 PUSH delivery, RFC 8936 POLL delivery).
Today the project ships a single extension — the receiver. Sibling extensions (transmitter etc.) will land alongside as they’re built.
Receiver
Lets a Quarkus app act as an SSF receiver against any spec-compliant transmitter — public providers like caep.dev, on-prem / self-hosted IdPs, or custom implementations.
What it does
-
Accepts inbound SETs via PUSH (RFC 8935) on a Vert.x route at
/ssf/push. -
Pulls SETs via POLL (RFC 8936) on a periodic Vert.x timer, with a manual
pollNow()entry point for app-driven cadence. -
Verifies every SET: JWS signature against the transmitter’s JWKS plus
iss/iat/jti/audchecks per RFC 8417. RS256-only + 2048-bit minimum RSA key by default (CAEP Interop §3.1) — both knobs are config-tunable. -
Manages stream lifecycle in two modes:
-
RECEIVER(default) — extension calls the transmitter’sconfiguration_endpointon startup to discover an existing stream or create a new one. -
TRANSMITTER— operator pre-creates the stream and provides astream-id.
-
-
Exposes
SsfStreamClientfor the full §8.1.x management surface (read / create / patch / replace / delete config, status read+update, add/remove subjects, request verification, POLL). -
Optional Micrometer metrics + per-event-type counters.
-
Outbound auth: static bearer token, self-contained OAuth2
client_credentials(no extra dep),quarkus-oidc-client, or no-op.
Installation
Add the extension to your Quarkus application:
<dependency>
<groupId>io.quarkiverse.openid-ssf</groupId>
<artifactId>quarkus-openid-ssf-receiver</artifactId>
<version>999-SNAPSHOT</version>
</dependency>
Quick start
The fastest end-to-end smoke test is against the public caep.dev transmitter — no Keycloak setup, no public tunnel, and no admin server of your own.
-
Sign in at https://ssf.caep.dev and copy the access token.
-
Open https://caep.dev/transmitter/events, set the audience and start the transmitter session.
-
Run the receiver-managed example in POLL mode:
export SSF_RECEIVER_TRANSMITTER_ACCESS_TOKEN=<your-caep-dev-token> export SSF_RECEIVER_EXPECTED_AUDIENCE=https://my-receiver.example/ssf mvn -pl receiver/examples/example-receiver-managed-stream quarkus:dev \ -Dquarkus.profile=caepdev \ -Dquarkus.openid-ssf.receiver.delivery-method=POLL \ -Dquarkus.openid-ssf.receiver.poll.interval=5s -
Fire an event from the caep.dev transmitter tab.
-
Inspect
curl -s localhost:28080/events/recent-events | jq.
See the
repository README
for the full walkthrough, including Keycloak setup, the consumer SPI
(SsfEventHandler), the usage-pattern catalogue, and operations notes.
Consumer SPI
Provide a CDI bean implementing SsfEventHandler:
@ApplicationScoped
public class MyHandler implements SsfEventHandler {
@Override
public void handle(SsfEventContext eventContext) {
if (eventContext.hasEvent("CaepSessionRevoked")) {
// … invalidate sessions for eventContext.eventToken().subjectId() …
}
}
}
Without an explicit handler, the default LoggingSsfEventHandler logs
jti / iss / event-type aliases at INFO.
Architecture
For the "why is the code shaped this way" view — module layout, startup
observer priorities, hot-path flow, build-time decisions — see
design/receiver.md.
Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
Master kill switch for the SSF receiver. When Useful for Note: Environment variable: |
boolean |
|
Issuer URL of the SSF transmitter (e.g. a Keycloak realm URL). Environment variable: |
required |
|
Expected Environment variable: |
string |
|
Who owns the stream lifecycle. Environment variable: |
|
|
Stream id pinned by the operator. Required when Environment variable: |
string |
|
How SETs are delivered. Environment variable: |
|
|
Path of the push endpoint, relative to Environment variable: |
string |
|
If set, the push endpoint requires this exact value in the inbound Environment variable: |
string |
|
Externally-reachable URL of the push endpoint, advertised to the transmitter as Environment variable: |
||
Poll endpoint URL. Optional override; if absent, the extension reads it from the stream’s Environment variable: |
||
If Environment variable: |
boolean |
|
Delay before the first poll fires after startup. Useful when other components need to warm up first (DB pool, config server, …). Defaults to Environment variable: |
|
|
How often to poll the transmitter. Defaults to 30s. Environment variable: |
|
|
Environment variable: |
int |
|
Environment variable: |
boolean |
|
If Environment variable: |
boolean |
|
Connect/read timeout for outbound poll requests. Defaults to 30s. Environment variable: |
|
|
If Set to Environment variable: |
boolean |
|
If Environment variable: |
boolean |
|
Optional human-readable description sent in the create-stream request. Environment variable: |
string |
|
Master switch for the Set to Environment variable: |
boolean |
|
Capacity of the default in-memory dedup store. Overflow evicts the oldest jti. Tune to roughly cover the longest expected redelivery window — defaults to Ignored by custom Environment variable: |
int |
|
Allowed JWS Environment variable: |
list of string |
|
Minimum RSA key size, in bits, accepted for SET signature verification. SETs signed with an RSA JWK whose modulus is shorter than this are rejected. Default is Environment variable: |
int |
|
If Set to Environment variable: |
boolean |
|
Explicit URL of the transmitter’s SSF metadata document. If unset, the URL is derived from
Set this property explicitly when the transmitter doesn’t follow the SSF rule — for example, OIDC-derived transmitters that serve the document at the OIDC-style appended path ( Environment variable: |
||
JWKS URL of the transmitter. Defaults to the Environment variable: |
||
Static bearer access token to send on outbound calls to the transmitter. If set, the deployment processor registers a Environment variable: |
string |
|
Event types this receiver wants to subscribe to. Required when Each entry can be a full URI (e.g. Example:
Environment variable: |
list of string |
|
Short aliases for event-type URIs — used as the
Built-in aliases for the OpenID SSF, CAEP 1.0, and RISC 1.0 spec event types are always registered out of the box (e.g. Environment variable: |
||
Short aliases for transmitter issuer URLs — used as the
No built-in defaults — issuer URLs are deployment-specific. Unknown URLs fall back to the URL itself as the tag value. Environment variable: |
||
Short, stable name for this receiver — surfaced as the Falls back to Environment variable: |
string |
|
Token endpoint URL where the OAuth2 grant is exchanged. Setting this activates the OAuth2 provider; leaving it empty falls through to the OIDC / no-op providers. Environment variable: |
||
OAuth2 grant type sent in the Environment variable: |
string |
|
Which client-authentication method to use when sending the
Environment variable: |
|
|
OAuth2 client identifier. Sent in the Environment variable: |
string |
|
OAuth2 client secret. Sent in the Environment variable: |
string |
|
Optional space-separated Environment variable: |
list of string |
|
Extra form parameters appended verbatim to the token-endpoint POST. Escape hatch for server-specific extensions (e.g. Environment variable: |
Map<String,String> |
|
Subtracted from the token endpoint’s reported Environment variable: |
|
|
Connect / read timeout for the token endpoint POST. Default is 5s. Environment variable: |
|
|
Maximum time to wait when fetching an access token from Environment variable: |
|
|
About the Duration format
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|