easyssf

Shared Signals Framework receivers for Java

easyssf lets your application react when your identity provider revokes a session, changes a credential or signals a risk. It verifies the security events of the OpenID Shared Signals Framework, rejects the access tokens and ends the sessions concerned, and hands everything else to your code. A Spring Boot starter and a Quarkus extension wire it into your application.

Experimental Java 21+ Spring Boot 4.1 Quarkus 3.27 Apache 2.0 OpenID conformance suite tested

How it works

A transmitter, usually your identity provider, delivers Security Event Tokens (SETs) to your application, either by pushing them to an endpoint or by letting the application poll for them. easyssf verifies each token, skips duplicates and routes the events to the integrations that act on them.

Events flow from the transmitter through the easyssf receiver to the resource server, OIDC client and custom handlers Transmitter identity provider, e.g. Keycloak signs SETs with its JWK Set push · RFC 8935 poll · RFC 8936 easyssf receiver verify the SET · RFC 8417 skip duplicates (jti) dispatch to event handlers stream management · metrics · JDBC state Resource server rejects revoked access tokens OIDC client ends the sessions concerned Your handlers step-up, audit, anything else

Push or pollRFC 8935 · RFC 8936

A push endpoint on a route of your choice, secured by its own stateless filter chain, or a poller that fetches and acknowledges SETs from the transmitter.

Every SET verifiedRFC 8417

Signature against the transmitter's JWK Set, discovered from its .well-known/ssf-configuration, plus typ, iss, aud, jti, iat and events. The transmitter and every endpoint it publishes must use HTTPS.

Resource server integration

A CAEP session-revoked event makes the application reject the access tokens of that session, or all tokens of the user issued before the event: built into the Spring Boot starter, a few lines with quarkus-oidc.

OIDC client integration

session-revoked and credential-change events invalidate the matching local sessions, by session id, subject or email.

Stream management

Let the application create or update its stream at the transmitter on startup, verify it, and use the whole stream management API through SsfStreamClient. Several transmitters, several Keycloak realms say, are each configured by name; the issuer of a SET selects the one that verifies it.

Production details covered

State in your database when there is one, Micrometer metrics, a health indicator for Actuator or SmallRye Health, retries with backoff, and the application starts even while the transmitter is down.

Getting started

easyssf comes as a starter for Spring Boot and, through the Quarkiverse extension quarkus-openid-ssf, as an extension for Quarkus. Both are built on the same receiver library and verify, de-duplicate and dispatch events the same way.

Three steps for a Spring Boot application with the servlet stack.

Add the starter

<dependency>
    <groupId>org.easyssf</groupId>
    <artifactId>easyssf-receiver-spring-boot-starter</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

No release yet: the snapshots of main are on the Maven Central snapshot repository, which your build has to enable.

Name the transmitter

easyssf:
  receiver:
    transmitter-issuer: https://idp.example/realms/demo
    expected-audience: https://my-app.example
    push:
      expected-auth-header: Bearer ${SSF_PUSH_SECRET}

Create the stream

Create a stream with push delivery at the transmitter that points to https://my-app.example/ssf/push and sends the configured header, or set easyssf.receiver.stream.management: receiver and let the application create it. With spring-boot-starter-security-oauth2-resource-server or spring-boot-starter-security-oauth2-client on the classpath, revoked sessions are handled from then on without further code.

React to any event

@Component
class StepUpHandler implements SsfEventHandler {

    @Override
    public void handle(SsfEventContext eventContext) {
        if (eventContext.hasEvent("CaepAssuranceLevelChange")) {
            SsfSubject subject = eventContext.subjectFor("CaepAssuranceLevelChange");
            Map<String, Object> event = eventContext.eventFor("CaepAssuranceLevelChange");
            // subject.subject(), subject.sessionId(), subject.email() ...
        }
    }

}

Handlers run before the SET is acknowledged. If one throws, the transmitter is expected to deliver the SET again.

Subjects as the spec defines them

SsfSubject is the sub_id of the SET: a subject identifier in any RFC 9493 format (iss_sub, email, opaque, account, phone_number, did, uri, aliases) or a complex subject with its user, session, device, tenant and other members. Shortcuts cover the common cases, nothing is lost.

Your own event type aliases

The SSF, CAEP and RISC event types have built-in aliases such as CaepSessionRevoked. Register aliases for vendor specific event types, in configuration or in code, and use them wherever an event type is named. The URIs stay canonical; an alias can never redefine another.

Test your receiver

easyssf-test brings a transmitter that runs inside your test JVM: it signs SETs, serves metadata and keys, hands out tokens and emulates the stream and poll endpoints. Push a revocation, assert the effect. The tests of the Spring Boot starter and of the Quarkus extension use it.

easyssf is experimental and not yet published to Maven Central. Build it from source with ./mvnw install and see the README for the complete configuration reference of the Spring Boot starter, and the README of quarkus-openid-ssf for the one of the Quarkus extension.

Modules

Everything is published under the group id org.easyssf. The Quarkus extension is a Quarkiverse project with its own coordinates.

ModuleWhat it isDepends on
easyssf-coreThe data structures of SSF shared by receivers and, later, transmitters: SETs, subjects, event types, stream configuration, transmitter metadata.nothing
easyssf-receiverThe receiver, independent of any framework: SET verification, de-duplication, event handlers, push handling, polling, stream management, token revocation and session termination logic.easyssf-core, Nimbus JOSE + JWT, SLF4J
easyssf-receiver-jdbcThe database-backed stores of the receiver, processed SETs and revocations, independent of any framework: the SQL, the schema and a small execution interface implemented over a DataSource or a framework's template.easyssf-receiver
easyssf-receiver-spring-boot-starterThe receiver for Spring Boot 4.1 with Spring Security 7.1 on the servlet stack: configuration properties, auto-configuration, push endpoint, resource server and OIDC client integration.easyssf-receiver, Spring Boot
easyssf-testTest support: a transmitter on a loopback port that signs and delivers SETs, serves metadata and keys and emulates the stream and poll endpoints, for the tests of your receiver.easyssf-core, Nimbus JOSE + JWT
easyssf-receiver-spring-boot-examplesAn example resource server and OIDC client with a Keycloak setup.
easyssf-test-conformanceRuns the OpenID conformance suite's SSF receiver test plans against a receiver under test, in any framework: the suite via Testcontainers, the scenarios the receiver plays, and the plan tests to extend.easyssf-receiver, Testcontainers, JUnit
easyssf-receiver-spring-boot-conformance-testsThe Spring Boot receiver under test and the four plan tests for it.
quarkus-openid-ssf-receiverThe receiver for Quarkus 3.27, group id io.quarkiverse.openid-ssf: configuration, CDI wiring, the Vert.x push route, the poll scheduler, token providers, Micrometer, SmallRye Health, JDBC, Dev UI and native image support. Its examples and conformance tests live in the same repository.easyssf-receiver, easyssf-receiver-jdbc, Quarkus

Without Spring Boot or Quarkus

easyssf-receiver has no framework dependencies. Assemble the parts you need and call them from the endpoint or scheduler of your framework. The Spring Boot starter and the Quarkus extension are two such integrations.

SsfHttpClient httpClient = new JdkSsfHttpClient();
String issuer = "https://idp.example/realms/demo";

SsfTransmitterMetadataResolver metadata = new SsfTransmitterMetadataResolver(issuer, null, httpClient);
NimbusSsfSetVerifier verifier = new NimbusSsfSetVerifier(issuer,
        () -> metadata.resolve().jwksUri().toString(), httpClient);
verifier.setExpectedAudience("https://my-app.example");

SsfEventHandler handler = (eventContext) -> {
    if (eventContext.hasEvent("CaepSessionRevoked")) {
        SsfSubject subject = eventContext.subjectFor("CaepSessionRevoked");
        // end the session subject.sessionId() of the user subject.subject()
    }
};
SsfSetProcessor processor = new SsfSetProcessor(verifier, new InMemorySsfJtiDedupStore(10_000), List.of(handler));

// PUSH: call this from the endpoint the transmitter posts SETs to
SsfPushHandler pushHandler = new SsfPushHandler(processor, "Bearer " + pushSecret);
SsfPushResponse response = pushHandler.handle(authorizationHeader, requestBody);

// POLL: fetch SETs from the transmitter instead
SsfPoller poller = new SsfPoller(httpClient, tokenProvider, () -> pollEndpoint, processor);
poller.start();

Interoperability

Keycloak

Tested with the SSF transmitter of Keycloak 26.8 (--features=ssf). The examples ship a pre-configured realm, and the README documents Keycloak's audience, scopes and single-stream rule.

OpenID conformance suite

The receiver test plans of the OpenID conformance suite, default and CAEP interop profile with push and poll delivery, run against the receiver in an automated test module.

Standards

Built on the OpenID Shared Signals specifications and the IETF security event RFCs, see Specifications for the complete list and what easyssf takes from each.

Not included yet: Spring WebFlux applications and long polling.

Specifications

What easyssf implements, and where each piece is defined.

SpecificationDefinesIn easyssf
OpenID Shared Signals Framework 1.0 Transmitters, receivers, streams, transmitter metadata discovery, stream verification Metadata discovery, stream management and verification, the SSF event types
OpenID CAEP 1.0 Continuous Access Evaluation Profile: session-revoked, credential-change, assurance-level-change and the other session and credential events Event type aliases, the resource server and OIDC client integrations
OpenID CAEP Interoperability Profile 1.0 The minimum a CAEP transmitter and receiver must support to work together The CAEP interop plans of the conformance tests
OpenID RISC Profile 1.0 Risk Incident Sharing and Coordination: account disabled, purged, credential compromise and the other account events Event type aliases, for your own handlers
RFC 8417 Security Event Token (SET): the JWT profile every event is delivered in Verification of signature, typ, iss, aud, jti, iat and events
RFC 8935 Push-based SET delivery over HTTP The push endpoint and its responses
RFC 8936 Poll-based SET delivery over HTTP The poller, acknowledgements and setErrs
RFC 9493 Subject identifiers for SETs: account, email, iss_sub, opaque, phone_number, did, uri, aliases SsfSubjectIdentifier, every format, and SsfSubject for complex subjects; the matching of events to sessions and users
RFC 7515 / RFC 7517 JSON Web Signature and JSON Web Key Signature checks against the transmitter's JWK Set, with Nimbus JOSE + JWT

News

Releases and other changes worth knowing about.

See the news page.

All news