OpenID Federation is a framework designed to facilitate the secure and interoperable interaction of entities within a federation. This involves the use of JSON Web Tokens (JWTs) to represent and convey necessary information for entities to participate in federations, ensuring trust and security across different organizations and systems.
- Federation: A group of organizations that agree to interoperate under a set of common rules defined in a federation policy.
- Entity Statements: JSON objects that contain metadata about entities (IdPs, RPs) and their federation relationships.
- Trust Chains: Mechanisms by which parties in a federation verify each other’s trustworthiness through a chain of entity statements, leading back to a trusted authority.
- Federation API: Interfaces defined for entities to exchange information and perform operations necessary for federation management.
- Federation Operator: The central authority in a federation that manages policy and trust chain verification.
- Identity Providers (IdPs): Entities that authenticate users and provide identity assertions to relying parties.
- Relying Parties (RPs): Entities that rely on identity assertions provided by IdPs to offer services to users.
- JSON Web Tokens (JWT): Used for creating verifiable entity statements and security assertions.
- JSON Object Signing and Encryption (JOSE): Standards for signing and encrypting JSON-based objects to ensure their integrity and confidentiality.
For seamless deployment of the OpenID Federation servers, Docker and Docker Compose offer the most efficient and straightforward approach.
docker compose build
- Compile the Docker images for the services.docker compose build --no-cache
- Compile the Docker images without utilizing the build cache, ensuring a clean build.
docker compose up
- Initiate all the services.docker compose up -d
- Launch all the services in detached mode, allowing them to run in the background.docker compose down
- Terminate the services.docker compose down -v
- Terminate the services and remove associated volumes.docker compose up db -d
- Start only the database container in detached mode for isolated database operations.docker compose up federation-server -d
- Start only the Federation Server in detached mode.
- Federation API: Accessible at http://localhost:8080
- Admin Server API: Accessible at http://localhost:8081
- Default Keycloak Server: Accessible at http://localhost:8082
This guide will help new users configure and deploy the OpenID Federation service, including setting up environment variables, the root entity, and necessary dependencies. Follow the steps outlined below.
Any changes affecting Entity Statements or Subordinate Statements must be explicitly published to take effect. This includes:
- Metadata changes
- Trust Mark modifications
- Configuration updates
- Key rotations
The Local Key Management Service is designed primarily for testing, development, and local experimentation purposes. It is not intended for use in production environments due to significant security and compliance risks.
The system comes with a preconfigured "root" account entity that responds to the root URL identifier's endpoints (
e.g., /.well-known/openid-federation
) and not tenant account endpoints. This account is used for managing
configurations specific to the root entity.
Set the following environment variables in your deployment environment. These variables are critical for configuring the service and connecting to the required resources.
APP_KEY=Nit5tWts42QeCynT1Q476LyStDeSd4xb
# A 32-byte random string that every deployer needs to create. It is used for application-level security.
ROOT_IDENTIFIER=http://localhost:8081
# The OpenID identifier of the root entity. It must be a valid URL hosting the well-known endpoint.
DATASOURCE_URL=jdbc:postgresql://db:5432/openid-federation-db
# The database instance URL. Defaults to the Docker Compose PostgreSQL instance.
DATASOURCE_USER=openid-federation-db-user
# The username for the database.
DATASOURCE_PASSWORD=openid-federation-db-password
# The password for the database.
DATASOURCE_DB=openid-federation-db
# The database name.
KMS_PROVIDER=local
# Defaults to the local KMS provider that runs on Docker. **Do not use in production.**
LOCAL_KMS_DATASOURCE_URL=jdbc:postgresql://local-kms-db:5432/openid-federation-local-kms-db
# The database instance URL for the local KMS.
LOCAL_KMS_DATASOURCE_USER=openid-federation-local-kms-db-user
# The username for the local KMS database.
LOCAL_KMS_DATASOURCE_PASSWORD=openid-federation-local-kms-db-password
# The password for the local KMS database.
LOCAL_KMS_DATASOURCE_DB=openid-federation-local-kms-db
# The database name for the local KMS.
KC_BOOTSTRAP_ADMIN_USERNAME=admin
# Default username for the local Keycloak OAuth2 provider.
KC_BOOTSTRAP_ADMIN_PASSWORD=admin
# Default password for the local Keycloak instance.
OAUTH2_RESOURCE_SERVER_JWT_ISSUER_URI=http://keycloak:8080/realms/openid-federation
# The JWT issuer URI for the local Keycloak instance.
- Replace default values (e.g.,
admin
,localhost
,password
) with secure values for production environments. - Ensure the
ROOT_IDENTIFIER
is a publicly accessible URL if deploying in a live environment. - Use a production-grade KMS provider in production environments instead of the local Docker-based KMS.
Once the environment variables are configured, you can start the OpenID Federation service stack using Docker Compose:
docker compose up
This command will initialize all necessary services, including the database, KMS provider, and Keycloak, as defined in the Docker Compose configuration file.
The admin endpoints are protected and require a valid JWT access token. To acquire one, follow these steps:
-
Send a POST Request to the Keycloak Token Endpoint:
POST http://localhost:8082/realms/openid-federation/protocol/openid-connect/token
-
Provide the Required Credentials in the Request Body:
Use
x-www-form-urlencoded
format with the following parameters:grant_type=client_credentials client_id=openid-client client_secret=th1s1s4s3cr3tth4tMUSTb3ch4ng3d
-
Example cURL Command:
curl -X POST http://localhost:8082/realms/openid-federation/protocol/openid-connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=openid-client" \ -d "client_secret=th1s1s4s3cr3tth4tMUSTb3ch4ng3d"
-
Parse the Response:
A successful request returns a JSON object. Extract the
access_token
field:{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 300, "token_type": "Bearer", "not-before-policy": 0, "scope": "openid" }
-
Use the Access Token in Subsequent API Requests:
Add the
access_token
to theAuthorization
header:Authorization: Bearer <access_token>
- Replace
client_secret
with a secure value in a production environment. - The token expires after a specified duration (
expires_in
field). Acquire a new token as needed.
To create a new tenant account, follow these steps. Any username can be used, and the same process in the following steps applies to all accounts, including the root account, which we will use as an example.
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/accounts/{username}
-
Include a JSON body with the desired account details. For example:
{ "username": "{username}", "identifier": "https://example.com/{username}" }
To delete an account by its username, follow these steps:
-
Send a
DELETE
request to the following endpoint:DELETE http://localhost:8081/accounts/{username}
-
Send a
POST
request to create a new key pair:POST http://localhost:8081/accounts/{username}/keys
-
Send a
GET
request to list the keys associated with the account:GET http://localhost:8081/accounts/{username}/keys
To revoke a key for an account, follow these steps:
-
Send a
DELETE
request to the following endpoint:DELETE http://localhost:8081/accounts/{username}/keys/{keyId}
-
Optionally, include a
reason
query parameter to specify the reason for revocation. For example:DELETE http://localhost:8081/accounts/{username}/keys/{keyId}?reason=Key+compromised
To assign metadata to your entity, follow these steps:
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/accounts/{username}/metadata
-
Include a JSON body with the metadata details. For example:
{ "key": "basic_metadata", "metadata": { "client_uri": "https://example.com", "contacts": [ "admin@example.com", "support@example.com" ] } }
-
Send a
GET
request to list all metadata for the entity:GET http://localhost:8081/accounts/{username}/metadata
-
Send a
DELETE
request to delete a metadata entry by its ID:DELETE http://localhost:8081/accounts/{username}/metadata/{id}
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/accounts/{username}/subordinates
-
Include a JSON body with the subordinate details. For example:
{ "identifier": "https://example.com/subordinate1" }
-
Send a
GET
request to list all subordinates for a given account:GET http://localhost:8081/accounts/{username}/subordinates
-
Send a
DELETE
request to delete a subordinate by its ID:DELETE http://localhost:8081/accounts/{username}/subordinates/{id}
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/accounts/{username}/subordinates/{subordinateId}/metadata
-
Include a JSON body with the metadata details. For example:
{ "key": "example_key", "metadata": { "description": "Example metadata description" } }
- Send a
GET
request to list all metadata for a subordinate:GET http://localhost:8081/accounts/{username}/subordinates/{subordinateId}/metadata
- Send a
DELETE
request to delete a metadata entry by its ID:DELETE http://localhost:8081/accounts/{username}/subordinates/{subordinateId}/metadata/{id}
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/accounts/{username}/subordinates/{id}/jwks
-
Include a JSON body with the JWKS details. For example:
{ "key": "example_key", "key_ops": ["sign", "verify"], "kty": "RSA" }
-
Send a
GET
request to list all JWKS for a subordinate:GET http://localhost:8081/accounts/{username}/subordinates/{id}/jwks
-
Send a
DELETE
request to delete a JWKS entry by its ID:DELETE http://localhost:8081/accounts/{username}/subordinates/{id}/jwks/{jwkId}
-
Send a
GET
request to retrieve the statement for a subordinate:GET http://localhost:8081/accounts/{username}/subordinates/{id}/statement
-
Send a
POST
request to publish a subordinate statement:POST http://localhost:8081/accounts/{username}/subordinates/{id}/statement
-
Optionally include a
dryRun
parameter in the request body to test the statement publication without making changes:{ "dryRun": true }
-
Send a
GET
request to retrieve the entity configuration statement:GET http://localhost:8081/accounts/{username}/entity-statement
-
Replace
{username}
with the specific account username for which you want to retrieve the entity configuration statement.
-
Send a
POST
request to publish the entity configuration statement:POST http://localhost:8081/accounts/{username}/entity-statement
-
Optionally, include a
dryRun
parameter in the request body to test the statement publication without making changes:{ "dryRun": true }
-
Replace
{username}
with the account username for which you want to publish the entity configuration statement.
sequenceDiagram
participant TA as Trust Anchor
participant TI as Trust Mark Issuer
participant H as Holder
TA->>TA: Create Trust Mark Type
TA->>TI: Authorize Issuer
TI->>H: Issue Trust Mark
H->>H: Store Trust Mark
Note over H: Publish new Entity Statement
# Create Trust Anchor account
POST http://localhost:8081/accounts
{
"username": "trust-anchor",
"identifier": "https://example.com/trust-anchor"
}
# Generate Trust Anchor keys
POST http://localhost:8081/accounts/trust-anchor/keys
# Create Trust Mark type
POST http://localhost:8081/accounts/trust-anchor/trust-mark-types
{
"identifier": "https://example.com/trust-mark-types/exampleType"
}
# Create Issuer account
POST http://localhost:8081/accounts
{
"username": "trust-mark-issuer",
"identifier": "https://example.com/issuer"
}
# Generate Issuer keys
POST http://localhost:8081/accounts/trust-mark-issuer/keys
# Authorize Issuer
POST http://localhost:8081/accounts/trust-anchor/trust-mark-types/{type-id}/issuers
{
"identifier": "https://example.com/issuer"
}
# Publish Trust Anchor configuration
POST http://localhost:8081/accounts/trust-anchor/entity-statement
# Issue Trust Mark
POST http://localhost:8081/accounts/trust-mark-issuer/trust-marks
{
"sub": "https://example.com/holder",
"trust_mark_type_identifier": "https://example.com/trust-mark-types/exampleType"
}
# Publish Issuer configuration
POST http://localhost:8081/accounts/trust-mark-issuer/entity-statement
# Create Holder account
POST http://localhost:8081/accounts
{
"username": "holder",
"identifier": "https://example.com/holder"
}
# Store Trust Mark
POST http://localhost:8081/accounts/holder/received-trust-marks
{
"trust_mark_type_identifier": "https://example.com/trust-mark-types/exampleType",
"jwt": "eyJ..."
}
# Publish Holder configuration
POST http://localhost:8081/accounts/holder/entity-statement
# Verify Trust Mark status
GET http://localhost:8080/trust-mark-issuer/trust-mark-status
{
"trust_mark_id": "https://example.com/trust-mark-types/exampleType",
"sub": "https://example.com/holder"
}
For the complete API documentation, please visit the API Reference
Apache License Version 2.0
- John Melati
- Niels Klomp
- Zoë Maas
- Sander Postma