OpenID Connect / Federation (v6.6)

See also:

  ▸ OpenID Connect client for Java

  ▸ Basic OIDC configuration and clients

  ▸ Advanced OIDC configuration settings


Since v6.4.0, pac4j supports OpenID Federation v1.0. Your application is the relying party (RP): it publishes its own entity configuration and uses trust anchors to resolve the OpenID Provider (OP). This support has been tested with Connect2id v19.8.

The same pac4j-oidc module handles federation; there is no additional pac4j dependency:

Example (Maven dependency):

<dependency>
    <groupId>org.pac4j</groupId>
    <artifactId>pac4j-oidc</artifactId>
    <version>${pac4j.version}</version>
</dependency>

Setting federation.targetOp enables federation for the OIDC client. It identifies the provider you want to resolve through the federation.

1) Federation endpoint

The federation endpoint publishes a signed entity configuration for your application. First, configure the keys used to sign it:

oidcConfig.getFederation().getKeystore().setPath("file:./metadata/oidcfede.keystore");
oidcConfig.getFederation().getKeystore().setKeystorePassword("changeit");
oidcConfig.getFederation().getKeystore().setPrivateKeyPassword("changeit");

KeystoreProperties holds the other keystore settings, such as the type and key alias.

oidcConfig.getFederation().getJwks().setPath("file:./metadata/oidcfede.jwks");
oidcConfig.getFederation().getJwks().setKid("mykeyoidcfede26");

For a filesystem path, pac4j creates the keystore or JWKS if the file does not exist.

OidcFederationProperties controls both the RP entity statement generated by the federation endpoint and the federation-based OP resolution.

Available properties are:

At least one signing source must be configured with a resource/path (jwks or keystore) to generate the entity configuration.

Use EntityConfigurationGenerator to serve the entity configuration from your application’s federation endpoint:

Spring Boot example:

    @RequestMapping(value = "/.well-known/openid-federation", produces = DefaultEntityConfigurationGenerator.CONTENT_TYPE)
    @ResponseBody
    public String oidcFederation() throws HttpAction {
        val oidcClient = (OidcClient) config.getClients().findClient("OidcClient").get();
        return oidcClient.getConfiguration().getFederation().getEntityConfigurationGenerator().generate();
    }

or

    @RequestMapping(value = "/.well-known/openid-federation")
    @ResponseBody
    public ResponseEntity<String> oidcFederation() throws HttpAction {
        val oidcClient = (OidcClient) config.getClients().findClient("OidcClient").get();
        val generator = oidcClient.getConfiguration().getFederation().getEntityConfigurationGenerator();
        val entityStatement = generator.generateEntityStatement();
        val contentType = generator.getContentType();
    return ResponseEntity.ok()
        .contentType(MediaType.parseMediaType(contentType))
        .body(entityStatement);
    }

2) Using trust anchors

In federation mode, leave discoveryURI unset. Give pac4j the target OP’s entity identifier and the trust anchors it should use to resolve that provider:

val federation = oidcConfig.getFederation();

federation.setTargetOp("http://localhost:8080/op");

val trust = new OidcTrustAnchorProperties();
trust.setIssuer("http://localhost:8081/ta");
trust.getJwks().setPath("http://localhost:8081/ta/jwks.json"); // optional
federation.getTrustAnchors().add(trust);

The federation metadata resolver performs a blocking load on first use, then refreshes metadata in the background when the trust chain expires.

3) Explicit / automatic client registration

If the RP is not yet registered and its clientId is left blank, pac4j supports both client registration modes and validates them against the OP metadata.

Priority is given to automatic mode if supported by the OP. In this case, the entity statement is sent via the client_assertion parameter in the authorization request URL.

Otherwise, if only explicit mode is supported and the federation_registration_endpoint exists, pac4j calls it to retrieve a client_id (and optionally a client_secret).

The client_id is displayed in the logs as follows: /!\ Explicit registration of the client 'http://rp' returned id: [XXX]. This information will not be repeated. You MUST manually add this value to your configuration before the next application startup!.

The client_secret is saved in the secretExportFile and must also be manually added to the configuration: /!\ The received secret has been saved into the file: YYY.