Welcome to the OpenID Federation project. This repository implements a Kotlin Multiplatform solution, enabling shared code across multiple platforms (such as the JVM, JS, and Native). Whether you are a developer familiar with Kotlin or new to multiplatform projects, this guide will help you understand the core concepts, architecture, and deployment instructions for the project.
OpenID Federation is a framework designed to facilitate secure and interoperable interactions among entities within a federation. It utilizes JSON Web Tokens (JWTs) to securely represent and transmit necessary metadata, ensuring trust and security across various organizations and systems.
- Federation: A group of organizations that agree to interoperate under a common set of rules defined in a federation policy.
- Entity Statements: JSON objects containing metadata about entities (such as Identity Providers and Relying Parties) along with their federation relationships.
- Trust Chains: Mechanisms by which entities verify one another's trustworthiness by following a chain of entity statements back to a trusted authority.
- Federation API: Standardized interfaces to exchange information and perform operations crucial for federation management.
- Federation Operator: The central authority in the federation responsible for policy management and trust chain verification.
- Identity Providers (IdPs): Entities that authenticate users and issue identity assertions to relying parties.
- Relying Parties (RPs): Entities that rely on the received identity assertions to provide services to users.
- JSON Web Tokens (JWT): Employed to create verifiable entity statements and security assertions.
- JSON Object Signing and Encryption (JOSE): Standards used for signing and encrypting JSON objects to ensure integrity and confidentiality.
This project is developed as a Kotlin Multiplatform project. The goal of this approach is to maximize code reuse and maintain consistency across different platforms:
- Common Code: Shared modules include the business logic and core functionality, written in pure Kotlin.
- Platform-Specific Implementations: Platform-dependent modules exist for code that needs to interact with specific operating system or ecosystem features.
- Supported Platforms: Typically, the project targets the JVM, JavaScript, and native environments. Consult the project’s build configuration for platform-specific details.
- Development Setup: Ensure you have the latest version of Kotlin and the appropriate SDKs installed for the platforms you intend to target.
For complete API details, please refer to the following resources:
We are using environment variables to configure certain components, like for instance the key management system. This is
independent of the deployment you choose, like using the
docker compose, or running the API servers directly on a JVM. In the root folder you will find
the .env.example file. Copy this file to .env
or .env.local
.
The docker compose method should automatically pick up the environment variables you put in there.
For seamless deployment of the OpenID Federation servers, Docker and Docker Compose are recommended. Docker provides an efficient and straightforward deployment and orchestration environment.
The following Docker Hub images are available for the OpenID Federation project:
- OpenID Federation Admin Server: https://hub.docker.com/r/sphereon/openid-federation-admin-server
- OpenID Federation Server: https://hub.docker.com/r/sphereon/openid-federation-server
These images can be used to quickly deploy the services in a containerized environment. You can use the docker compose
file to pull these images, or you can pull them directly using docker pull
.
-
docker compose build
Compile the Docker images for the services. -
docker compose build --no-cache
Build the Docker images without using the cache to ensure a clean build.
-
docker compose up
Initiate all services. -
docker compose up -d
Launch all services in detached mode (running in the background). -
docker compose down
Terminate all running services. -
docker compose down -v
Terminate services and remove associated volumes. -
docker compose up db -d
Start only the database container in detached mode. -
docker compose up openid-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 (in-memory) 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.
This README show the steps to interact with the API down below. You can use a tool like CURL or use the Swagger UI to interact with the API. We have also included a postman collection you can import into Postman. It contains examples for most endpoints and also at the toplevel has an OAUth2 integration. So you can get an access token from the toplevel folder and then use that token automatically in subsequent calls.
Set the following environment variables in your deployment environment. These variables are critical for configuring the service and connecting to the required resources.
Note: See the notes above about copying the .env.example to .env or .env.local first
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.
See .env.example for all the allowd values and their explanations
The service supports multiple KMS providers. Use the environment variable KMS_PROVIDER
to select the desired provider:
memory
: In-memory KMS (for development and testing only!)aws
: AWS Key Management Serviceazure
: Azure Key Vault
KMS_PROVIDER=memory
# When set to 'memory', the service uses a local in-memory key store for encryption and decryption.
Note: The memory KMS is not storing any private keys!. It is in memory, which means they will be gone after a reboot. This was done on purpose, as you should use an external KMS system with proper protection like Azure Keyvault or AWS KMS.
When configured like below, AWS Key Management Service will be used. You need to provide your AWS region, and you will also need to provide an access key id and secret.
Creating an AWS Access Key and Secret Access Key
-
Sign In to AWS Management Console Use your AWS account credentials to sign in at https://aws.amazon.com/.
-
Navigate to IAM (Identity and Access Management)
- In the AWS Management Console, search for IAM or select it from the list of services.
- IAM lets you manage users, groups, roles, and their associated access credentials.
-
Create or Select an IAM User
- If you already have a user for programmatic access, select that user from the Users tab.
- Otherwise, to create a new user:
- Click on Add User.
- Enter a username.
- Under Select AWS Access Type, check Programmatic access (this is needed to generate an access key and secret key).
-
Set Permissions
- For initial testing of key management tasks, you could attach existing policies like * AWSKeyManagementServicePowerUser. This policy grants broad administrative permissions for KMS, allowing the creation and management of keys.
- Important: The
AWSKeyManagementServicePowerUser
policy does not grant permissions to perform cryptographic signing operations. To enable signing operation, you must explicitly grant thekms:Sign
permission. - For production environments, always follow the principle of least privilege, granting only the necessary permissions for the required tasks.
-
Review and Create User
- Complete the steps and on the final screen, AWS will display an Access Key ID and Secret Access Key.
- Important: Save your secret access key securely as it is shown only once. You can download the credentials as a CSV file.
Use the values from step 5 to set the Environment variables:
KMS_PROVIDER=aws
# Use 'aws' to select AWS Key Management Service.
AWS_APPLICATION_ID=your-own-id
# The ID is only used internally and is not related to anything in AWS itself
AWS_REGION=your-aws-region-like-eu-west-1
# The AWS region where the KMS key is located. Example: eu-west-2
AWS_ACCESS_KEY_ID=your-aws-access-key
# Your AWS access key ID from step 5.
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key
# Your AWS secret access key from step 5.
Optional environment variables for AWS:
AWS_MAX_RETRIES=5
# The maximum number of retries for operations against AWS KMS, defaults to 10.
AWS_BASE_DELAY=200
# The base delay (in milliseconds) before retrying a failed operation, defaults to 500
AWS_MAX_DELAY=2000
# The maximum delay (in milliseconds) to wait between retries, defaults to 15000
KMS_PROVIDER=azure
# Use 'azure' to select Azure Key Vault as the KMS.
AZURE_KEYVAULT_APPLICATION_ID=your-azure-app-id
# The Azure application (client) ID used to authenticate with Azure Key Vault.
AZURE_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net/
# The URL for your Azure Key Vault instance.
AZURE_KEYVAULT_TENANT_ID=your-azure-tenant-id
# The Azure tenant ID used for authentication.
AZURE_KEYVAULT_CLIENT_ID=your-keyvault-client-id
# The client ID for the Azure Key Vault application.
AZURE_KEYVAULT_CLIENT_SECRET=your-keyvault-client-secret
# The client secret corresponding to the Azure Key Vault client.
Optional environment variables for Azure Keyvault:
AZURE_KEYVAULT_MAX_RETRIES=5
# The maximum number of retries for operations against Azure Key Vault, defaults to 10
AZURE_KEYVAULT_BASE_DELAY=200
# The base delay (in milliseconds) before retrying a failed operation, defaults to 500
AZURE_KEYVAULT_MAX_DELAY=2000
# The maximum delay (in milliseconds) to wait between retries, defaults to 15000
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. - Select the appropriate KMS provider based on your environment:
- For development or testing, the in-memory KMS is sufficient. Note: Keys are ephemeral and thus will be gone after a restart/reboot
- For production, use AWS KMS or Azure Key Vault to ensure robust security for key management.
- Never commit sensitive credentials (such as AWS or Azure secrets) into version control.
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 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:
/
3. **Example cURL Command**:
```bash
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:
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/accounts
-
Include a JSON body with the desired account details. For example:
{ "username": "{username}", "identifier": "http://localhost:8081/{username}" }
Note: All subsequent requests will use the X-Account-Username
header to specify the account context. If not provided,
it defaults to the root account.
To delete a tenant account, follow these steps:
-
Send a
DELETE
request to the following endpoint:DELETE http://localhost:8081/accounts X-Account-Username: {username} # root account cannot be deleted
-
Send a
POST
request to create a new key pair:POST http://localhost:8081/keys X-Account-Username: {username} # Optional, defaults to root
You can use the param kms_key_ref to assign a name to the key. This value will not end up in the JWK itself, but you can use it for selecting which key to sign with. The kid value could also be used, but that is typically not known upfront as in most cases it is generated as the SHA1 thumbprint of the JWK.
You can also provide a JOSE/JWA signature algorithm provided the KMS configured supports it. Typical values are ES256, ES384, ES512
{
"kmsKeyRef": "my-key",
"signatureAlgorithm": "ES256"
}
-
Send a
GET
request to list the keys:GET http://localhost:8081/keys X-Account-Username: {username} # Optional, defaults to root
-
Send a
DELETE
request to revoke a key:DELETE http://localhost:8081/keys/{keyId} X-Account-Username: {username} # Optional, defaults to root
-
Optionally, include a
reason
query parameter to specify the reason for revocation:DELETE http://localhost:8081/keys/{keyId}?reason=Key+compromised X-Account-Username: {username} # Optional, defaults to root
To assign metadata to your entity, follow these steps:
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/metadata X-Account-Username: {username} # Optional, defaults to root
-
Include a JSON body with the metadata details. For example:
{ "key": "basic_metadata", "metadata": { "client_uri": "http://localhost:8081", "contacts": [ "admin@example.com", "support@example.com" ] } }
-
Send a
GET
request to list all metadata:GET http://localhost:8081/metadata X-Account-Username: {username} # Optional, defaults to root
-
Send a
DELETE
request to delete a metadata entry by its ID:DELETE http://localhost:8081/metadata/{id} X-Account-Username: {username} # Optional, defaults to root
Authority Hints are used to indicate which authorities an entity recognizes in the federation. These authorities can validate trust chains and issue trust marks.
Send a GET request to retrieve all authority hints for an account:
GET http://localhost:8081/authority-hints
X-Account-Username: {username} # Optional, defaults to root
Send a POST request to add a new authority hint:
POST http://localhost:8081/authority-hints
X-Account-Username: {username} # Optional, defaults to root
{
"identifier": "http://localhost:8081/authority-hints"
}
Send a DELETE request to remove an authority hint by its ID:
DELETE http://localhost:8081/authority-hints/{id}
X-Account-Username: {username} # Optional, defaults to root
Remember to publish your entity configuration after making changes to authority hints for them to take effect.
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/subordinates X-Account-Username: {username} # Optional, defaults to root
-
Include a JSON body with the subordinate details. For example:
{ "identifier": "http://localhost:8081/subordinate1" }
-
Send a
GET
request to list all subordinates:GET http://localhost:8081/subordinates X-Account-Username: {username} # Optional, defaults to root
-
Send a
DELETE
request to delete a subordinate by its ID:DELETE http://localhost:8081/subordinates/{id} X-Account-Username: {username} # Optional, defaults to root
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/subordinates/{subordinateId}/metadata X-Account-Username: {username} # Optional, defaults to root
-
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/subordinates/{subordinateId}/metadata X-Account-Username: {username} # Optional, defaults to root
- Send a
DELETE
request to delete a metadata entry by its ID:DELETE http://localhost:8081/subordinates/{subordinateId}/metadata/{id} X-Account-Username: {username} # Optional, defaults to root
-
Send a
POST
request to the following endpoint:POST http://localhost:8081/subordinates/{id}/jwks X-Account-Username: {username} # Optional, defaults to root
-
Include a JSON body with the JWKS details. For example:
{ "kid": "example_key", "key_ops": ["sign", "verify"], "kty": "EC" }
-
Send a
GET
request to list all JWKS for a subordinate:GET http://localhost:8081/subordinates/{id}/jwks X-Account-Username: {username} # Optional, defaults to root
-
Send a
DELETE
request to delete a JWKS entry by its ID:DELETE http://localhost:8081/subordinates/{id}/jwks/{jwkId} X-Account-Username: {username} # Optional, defaults to root
-
Send a
GET
request to retrieve the statement for a subordinate:GET http://localhost:8081/subordinates/{id}/statement X-Account-Username: {username} # Optional, defaults to root
-
Send a
POST
request to publish a subordinate statement:POST http://localhost:8081/subordinates/{id}/statement X-Account-Username: {username} # Optional, defaults to root
-
Optionally include a
kmsKeyRef
parameter if you want to sign with a specific key. kmsKeyRef always overrideskid
if both are supplied. If none are specified the first key available will be used -
Optionally include a
kid
parameter if you want to sign with a specific key. The kid is typically the sha1 thumbprint of the key, and generated by the underlying KMS. Opposed to the kmsKeyRef which you choose yourself . -
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/entity-statement X-Account-Username: {username} # Optional, defaults to root
-
Send a
POST
request to publish the entity configuration statement:POST http://localhost:8081/entity-statement X-Account-Username: {username} # Optional, defaults to root
-
Optionally include a
kmsKeyRef
parameter if you want to sign with a specific key. kmsKeyRef always overrideskid
if both are supplied. If none are specified the first key available will be used -
Optionally include a
kid
parameter if you want to sign with a specific key. The kid is typically the sha1 thumbprint of the key, and generated by the underlying KMS. Opposed to the kmsKeyRef which you choose yourself. -
Optionally, include a
dryRun
parameter in the request body to test the statement publication without making changes:{ "dryRun": true }
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
Content-Type: application/json
{
"username": "trust-anchor",
"identifier": "http://localhost:8080/trust-anchor"
}
# Generate Trust Anchor keys
POST http://localhost:8081/keys
X-Account-Username: trust-anchor
# Create Trust Mark type
POST http://localhost:8081/trust-mark-types
X-Account-Username: trust-anchor
Content-Type: application/json
{
"identifier": "http://localhost:8080/trust-anchor/trust-mark-types/exampleType"
}
# Create Issuer account
POST http://localhost:8081/accounts
Content-Type: application/json
{
"username": "trust-mark-issuer",
"identifier": "http://localhost:8080/trust-mark-issuer"
}
# Generate keys for the Issuer
POST http://localhost:8081/keys
X-Account-Username: trust-mark-issuer
# Publish Issuer configuration
POST http://localhost:8081/entity-statement
X-Account-Username: trust-mark-issuer
# Authorize Issuer using Trust Anchor account
POST http://localhost:8081/trust-mark-types/{trust-mark-type-id}/issuers
X-Account-Username: trust-anchor
Content-Type: application/json
{
"identifier": "http://localhost:8080/trust-mark-issuer"
}
# Publish Trust Anchor configuration
POST http://localhost:8081/entity-statement
X-Account-Username: trust-anchor
# Issue Trust Mark to holder
POST http://localhost:8081/trust-marks
X-Account-Username: trust-mark-issuer
Content-Type: application/json
{
"sub": "http://localhost:8080/trust-mark-holder",
"trust_mark_id": "http://localhost:8080/trust-mark-types/exampleType"
}
# Create Holder account
POST http://localhost:8081/accounts
Content-Type: application/json
{
"username": "trust-mark-holder",
"identifier": "http://localhost:8080/trust-mark-holder"
}
# Generate keys for the Holder
POST http://localhost:8081/keys
X-Account-Username: trust-mark-holder
# Store received Trust Mark
POST http://localhost:8081/received-trust-marks
Content-Type: application/json
X-Account-Username: trust-mark-holder
{
"trust_mark_id": "http://localhost:8080/trust-mark-types/exampleType",
"jwt": "eyJ..." # Replace with JWT token issued in step 4
}
# Publish Holder configuration
POST http://localhost:8081/entity-statement
X-Account-Username: trust-mark-holder
# Check Trust Mark status
POST http://localhost:8080/trust-mark-issuer/trust-mark-status
Content-Type: application/json
{
"trust_mark_id": "http://localhost:8080/trust-mark-types/exampleType",
"sub": "http://localhost:8080/trust-mark-holder"
}
Apache License Version 2.0
- John Melati
- Niels Klomp
- Zoë Maas
- Sander Postma