Subordinate Management
Subordinates are entities that register with the Trust Anchor to participate in the federation. The Trust Anchor issues signed subordinate statements that establish trust chains.
Understanding Subordinates
A subordinate can be:
OpenID Provider (OP): Identity providers that authenticate users
Relying Party (RP): Applications that rely on OPs for authentication
Intermediate Authority (IA): Sub-Trust Anchors in hierarchical federations
When an entity registers as a subordinate:
The TA verifies the entity’s self-signed configuration
The TA checks that it’s listed in the entity’s
authority_hintsThe TA validates metadata against its policy
The TA creates and signs a subordinate statement
The statement is stored and made available via the
/fetchendpoint
Prerequisites
Before registering a subordinate, the entity must:
Publish Entity Configuration: The entity must serve a signed JWT at
/.well-known/openid-federationInclude Authority Hints: The entity’s configuration must include your Trust Anchor in its
authority_hintsProvide Valid Metadata: The entity must have valid OpenID Federation metadata
Example Entity Configuration (simplified):
{
"iss": "https://example-rp.com",
"sub": "https://example-rp.com",
"iat": 1705315200,
"exp": 1736851200,
"authority_hints": ["https://federation.example.com"],
"jwks": {
"keys": ["...key data..."]
},
"metadata": {
"openid_relying_party": {
"redirect_uris": ["https://example-rp.com/callback"],
"response_types": ["code"],
"grant_types": ["authorization_code"]
}
}
}
Registering a Subordinate
Basic Registration
Fetch the entity’s configuration and register it:
# Step 1: Fetch entity configuration
ENTITY_JWT=$(curl -s https://example-rp.com/.well-known/openid-federation)
# Step 2: Extract metadata and JWKS (using Python)
python3 << 'EOF'
import json
from jwcrypto import jwt
entity_jwt = "..." # paste the JWT
jose = jwt.JWT.from_jose_token(entity_jwt)
payload = json.loads(jose.token.objects["payload"])
print(json.dumps({
"entityid": payload["sub"],
"metadata": payload["metadata"],
"jwks": payload["jwks"],
"forced_metadata": {}
}))
EOF
# Step 3: Register with the TA
curl -X POST http://localhost:8000/api/v1/subordinates \
-H "Content-Type: application/json" \
-d '{
"entityid": "https://example-rp.com",
"metadata": {
"openid_relying_party": {
"redirect_uris": ["https://example-rp.com/callback"],
"response_types": ["code"],
"grant_types": ["authorization_code"]
}
},
"jwks": {
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "...",
"kid": "key-1"
}
]
},
"forced_metadata": {}
}'
Response (201 Created):
{
"id": 1,
"entityid": "https://example-rp.com",
"metadata": {"openid_relying_party": {"redirect_uris": ["..."]}},
"forced_metadata": {},
"jwks": {"keys": ["..."]},
"required_trustmarks": null,
"valid_for": 8760,
"expire_at": "2027-01-15T12:00:00Z",
"autorenew": true,
"active": true,
"additional_claims": null
}
Validation Process
During registration, the API performs these validations:
Fetch Entity Configuration: Downloads and parses the entity’s
/.well-known/openid-federationJWTVerify Signature: Validates the JWT signature using the provided JWKS. If the entity’s configuration uses
jwks_uriinstead of inlinejwks, the keys are fetched from the URI automaticallyCheck Authority Hints: Confirms your TA domain is in the entity’s
authority_hintslistValidate Metadata Policy: Applies your TA’s metadata policy to ensure the entity’s metadata conforms to your requirements
Create Statement: Generates a signed subordinate statement
Registration with Forced Metadata
Use forced_metadata to override or add metadata values that the TA
enforces for all statements:
curl -X POST http://localhost:8000/api/v1/subordinates \
-H "Content-Type: application/json" \
-d '{
"entityid": "https://example-op.com",
"metadata": {
"openid_provider": {
"issuer": "https://example-op.com",
"authorization_endpoint": "https://example-op.com/authorize",
"token_endpoint": "https://example-op.com/token"
}
},
"jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "...", "y": "..."}]},
"forced_metadata": {
"openid_provider": {
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["ES256", "RS256"]
}
}
}'
The forced_metadata is merged into the subordinate statement, overriding
any conflicting values from the entity’s own metadata.
Registration with Additional Claims
Add custom claims to the subordinate statement:
curl -X POST http://localhost:8000/api/v1/subordinates \
-H "Content-Type: application/json" \
-d '{
"entityid": "https://example-rp.com",
"metadata": {"openid_relying_party": {"redirect_uris": ["..."]}},
"jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "...", "y": "..."}]},
"forced_metadata": {},
"additional_claims": {
"organization_name": "Example Corp",
"registration_date": "2024-01-15",
"sector": "finance"
}
}'
Custom Validity Period
Set a custom validity period (cannot exceed system default):
curl -X POST http://localhost:8000/api/v1/subordinates \
-H "Content-Type: application/json" \
-d '{
"entityid": "https://example-rp.com",
"metadata": {"openid_relying_party": {"redirect_uris": ["..."]}},
"jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "...", "y": "..."}]},
"forced_metadata": {},
"valid_for": 720
}'
Viewing Subordinates
List All Subordinates
curl http://localhost:8000/api/v1/subordinates
Response:
{
"count": 3,
"items": [
{
"id": 1,
"entityid": "https://example-rp.com",
"metadata": {"openid_relying_party": {"redirect_uris": ["..."]}},
"forced_metadata": {},
"jwks": {"keys": ["..."]},
"required_trustmarks": null,
"valid_for": 8760,
"expire_at": "2027-01-15T12:00:00Z",
"autorenew": true,
"active": true,
"additional_claims": null
}
]
}
Get Subordinate by ID
curl http://localhost:8000/api/v1/subordinates/1
Via Trust Anchor (Public)
External parties can list subordinates via the Trust Anchor API:
# List all subordinates
curl https://federation.example.com/list
# List only OpenID Providers
curl "https://federation.example.com/list?entity_type=openid_provider"
# List only Relying Parties
curl "https://federation.example.com/list?entity_type=openid_relying_party"
# List subordinates with a specific trust mark
curl "https://federation.example.com/list?trust_mark_type=https://example.com/trustmarks/member"
Updating Subordinates
Update a subordinate’s configuration. The metadata, forced_metadata,
and jwks fields are required for every update:
curl -X POST http://localhost:8000/api/v1/subordinates/1 \
-H "Content-Type: application/json" \
-d '{
"metadata": {"openid_relying_party": {"redirect_uris": ["..."]}},
"forced_metadata": {
"openid_relying_party": {
"application_type": "web"
}
},
"jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "...", "y": "..."}]},
"autorenew": false,
"valid_for": 8760,
"active": true
}'
The update process:
Re-fetches the entity’s current configuration
Re-validates against TA policy
Creates a new signed subordinate statement
Updates database and Redis
Disabling Subordinates
Disable a subordinate without removing it:
curl -X POST http://localhost:8000/api/v1/subordinates/1 \
-H "Content-Type: application/json" \
-d '{
"metadata": {"openid_relying_party": {"redirect_uris": ["..."]}},
"forced_metadata": {},
"jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "...", "y": "..."}]},
"active": false
}'
When active is false:
The subordinate statement is no longer served via
/fetchThe entity is removed from
/listresultsExisting trust chains become invalid
Subordinate Statement Structure
The Trust Anchor creates subordinate statements with this structure:
{
"iss": "https://federation.example.com",
"sub": "https://example-rp.com",
"iat": 1705315200,
"exp": 1736851200,
"jwks": {
"keys": ["...key data..."]
},
"metadata": {
"openid_relying_party": {
"redirect_uris": ["https://example-rp.com/callback"],
"application_type": "web"
}
},
"metadata_policy": {},
"organization_name": "Example Corp"
}
Key fields:
iss: Trust Anchor entity IDsub: Subordinate entity IDjwks: Entity’s public keys (for chain verification)metadata: Merged and policy-applied metadatametadata_policy: Any policy the subordinate should apply to its subordinates
Fetching Subordinate Statements
External parties fetch subordinate statements via the Trust Anchor:
curl "https://federation.example.com/fetch?sub=https://example-rp.com"
Response: Signed subordinate statement JWT
Resolving Trust Chains
The /resolve endpoint builds complete trust chains:
curl "https://federation.example.com/resolve?sub=https://example-rp.com&trust_anchor=https://federation.example.com"
Response: Resolution JWT containing:
Final resolved metadata (after policy application)
Complete trust chain (array of JWTs)
The resolve endpoint supports entities that publish jwks_uri instead of
inline jwks in their entity configurations. When an authority in the
chain lacks inline keys, the resolver fetches them from the jwks_uri
automatically. Fetched JWKS responses are cached in Redis for 1 hour to
avoid repeated network round-trips during chain resolution.
Entity Types
Subordinates are categorized by their metadata:
OpenID Relying Party
Entities with openid_relying_party metadata:
{
"metadata": {
"openid_relying_party": {
"redirect_uris": ["https://example.com/callback"],
"response_types": ["code"],
"grant_types": ["authorization_code"],
"client_name": "Example Application"
}
}
}
OpenID Provider
Entities with openid_provider metadata:
{
"metadata": {
"openid_provider": {
"issuer": "https://example-op.com",
"authorization_endpoint": "https://example-op.com/authorize",
"token_endpoint": "https://example-op.com/token",
"jwks_uri": "https://example-op.com/jwks"
}
}
}
Metadata Policy
The Trust Anchor can enforce metadata policy on subordinates.
Configure policy in localsettings.py:
POLICY_DOCUMENT = {
"metadata_policy": {
"openid_relying_party": {
"grant_types": {
"subset_of": ["authorization_code", "refresh_token"]
},
"response_types": {
"subset_of": ["code"]
}
},
"openid_provider": {
"subject_types_supported": {
"subset_of": ["public", "pairwise"]
},
"id_token_signing_alg_values_supported": {
"superset_of": ["ES256"]
}
}
}
}
Policy Operators
subset_of: Value must be subset of allowed valuessuperset_of: Value must include all required valuesone_of: Single value must be one of allowed valuesadd: Add values to the claimdefault: Default value if claim is missingessential: Claim is required
When an entity’s metadata violates the policy, registration fails with a 400 error.
Renewing Subordinates
Renewing a subordinate re-fetches and re-verifies its entity configuration, regenerates the signed subordinate statement with a fresh expiry, and updates both the database and Redis.
Renew a Single Subordinate (API)
curl -X POST http://localhost:8000/api/v1/subordinates/1/renew
The endpoint:
Re-fetches the entity’s
/.well-known/openid-federationJWTVerifies the signature using the stored JWKS
Checks that the TA domain is still in
authority_hintsValidates metadata policy merge and application
Regenerates the signed subordinate statement
Updates the database and Redis
Response (200 OK): The updated subordinate object.
Error responses:
400: Entity configuration fetch failed, authority_hints mismatch, or policy violation404: Subordinate not found
Renew All Active Subordinates (Management Command)
The renew_subordinates management command renews all active subordinates
in a single run. Each subordinate is processed independently — a failure on
one does not stop the others.
docker compose exec admin python manage.py renew_subordinates
Output:
Renewing https://example-rp.com ... OK
Renewing https://example-op.com ... OK
Renewing https://inactive-entity.com ... FAILED (fetch: connection refused)
Done: 2/3 renewed, 1 failed.
For production, schedule this command to run periodically. See Subordinate Renewal in the deployment guide for cron and systemd timer examples.
Auto-Renewal
Enable auto-renewal to keep subordinate statements fresh:
curl -X POST http://localhost:8000/api/v1/subordinates/1 \
-H "Content-Type: application/json" \
-d '{
...,
"autorenew": true
}'
With auto-renewal:
The statement is refreshed before expiry
Entity configuration is re-fetched and re-validated
New signed statement is generated
Workflow Example
Complete workflow for onboarding a new subordinate:
Entity Preparation
The entity must:
Generate signing keys
Create entity configuration with your TA in
authority_hintsPublish at
/.well-known/openid-federation
Verification (Manual)
Verify the entity meets your federation’s requirements before registration.
Fetch Entity Configuration
Use the fetch-config endpoint to retrieve and validate the entity’s metadata and keys:
curl -X POST http://localhost:8000/api/v1/subordinates/fetch-config \ -H "Content-Type: application/json" \ -d '{"url": "https://new-entity.example.com"}'
This returns the entity’s
metadata,jwks,jwks_uri,authority_hints, andtrust_marksafter signature validation.If the entity publishes a
jwks_uriinstead of inlinejwks, the endpoint automatically fetches the keys from the URI and returns them as inlinejwksin the response. This means the registration step always receives populated keys regardless of how the entity publishes them.Registration
Use the metadata and JWKS from the fetch-config response:
curl -X POST http://localhost:8000/api/v1/subordinates \ -H "Content-Type: application/json" \ -d '{ "entityid": "https://new-entity.example.com", "metadata": {"openid_relying_party": {"redirect_uris": ["..."]}}, "jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "...", "y": "..."}]}, "forced_metadata": {} }'
Issue Trust Marks (optional)
curl -X POST http://localhost:8000/api/v1/trustmarks \ -H "Content-Type: application/json" \ -d '{ "tmt": 1, "domain": "https://new-entity.example.com" }'
Verification
Confirm the entity appears in listings:
curl https://federation.example.com/listVerify the subordinate statement:
curl "https://federation.example.com/fetch?sub=https://new-entity.example.com"
Troubleshooting
Registration Fails with 400
Authority hints missing: Entity’s configuration must include your TA in
authority_hintsPolicy violation: Entity’s metadata doesn’t conform to your policy
Signature verification failed: JWKS doesn’t match the entity’s published configuration
Registration Fails with 403
Entity is already registered. Use the update endpoint instead.
Entity Not Appearing in List
Check that
activeis trueVerify the subordinate statement was created
Check Redis contains the entity data
Check entity type filtering:
# Verify entity type in metadata
curl http://localhost:8000/api/v1/subordinates/1
# Try different entity_type filters
curl "https://federation.example.com/list?entity_type=openid_relying_party"