Credex Usage
This guide covers how to use Credex as a policy-backed credential broker to bridge OAuth and SPIFFE systems across one or more trust domains.
HTTP API
Section titled “HTTP API”The Credex HTTP API surface is summarised in the following table:
| Method | Path | Description |
|---|---|---|
POST | / | SPIFFE Token Exchange |
POST | /token | OAuth AS token exchange (RFC 8693) |
GET | /.well-known/openid-configuration | OIDC provider metadata |
GET | /.well-known/oauth-authorization-server | OAuth AS metadata (RFC 8414) |
GET | /keys | JWKS for verifying issued tokens |
GET | /health | Liveness/readiness probe |
Exchange Types
Section titled “Exchange Types”Credex is a flexible security token service which can be used for various purposes.
The type of token exchange performed is determined by request parameters in a given exchange request, such as which tokens the client presents and which it omits.
Broadly speaking, the use cases for Credex can be broken down into three different exchange types based on their semantics and intended use cases:
- Impersonation exchanges
- Delegation exchanges
- OIDC to SPIFFE exchanges.
Impersonation
Section titled “Impersonation”An RFC 8693-compliant request where the client presents a subject token and no actor token, and receives an access token that carries only the subject’s identity.
This is appropriate when the calling workload is genuinely the principal: there is no separate user or upstream identity to preserve.
sequenceDiagram
participant W as Workload
participant C as Credex
participant S as Downstream service
W->>C: POST /token (client_assertion, subject_token, audience, scope)
C->>C: Validate client and subject token
C->>C: Evaluate policy and grant scopes
C-->>W: access_token { sub: workload }
W->>S: Authorization: Bearer access_token
S->>S: sub = workload
In this scenario, the workload presents its own JWT-SVID as both client_assertion and subject_token.
For example:
POST /tokenContent-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe&client_assertion=<JWT-SVID, aud=https://credex.example.com/token>&subject_token=<JWT-SVID, aud=https://credex.example.com/token>&subject_token_type=urn:ietf:params:oauth:token-type:jwt_spiffe&audience=https://api.example.com&scope=data:readand the response would include a token with a sub claim but no act claim:
{ "iss": "https://credex.example.com", "sub": "spiffe://example.com/<workload-identifier>", "aud": "https://api.example.com", "exp": 1796000600, "iat": 1796000000}The resulting token would then be used to authenticate to https://api.example.com, assuming the API is configured to trust Credex-issued tokens.
Delegation
Section titled “Delegation”Similar to the impersonation request, except that the client presents both a subject token (the principal on whose behalf the exchange is happening) and an actor token (the workload performing the action).
The issued access token preserves both identities: the subject in sub and the actor in a nested act claim.
sequenceDiagram
participant A as Agent workload
participant C as Credex
participant S as Downstream service
A->>C: POST /token (subject_token, actor_token, client_assertion, audience, scope)
C->>C: Validate subject and actor tokens
C->>C: Evaluate delegation policy
C-->>A: access_token { sub: user, act: { sub: agent } }
A->>S: Authorization: Bearer access_token
S->>S: sub = user, act.sub = agent
In the above diagram, the Agent workload holds the subject token (user OIDC token or upstream AS access token) and its own JWT-SVID (used as both client_assertion and actor_token)
Delegation is the right choice whenever the calling workload is acting on behalf of someone else, for example an agent acting on behalf of a user, or a service-bus consumer acting on behalf of a publisher. Downstream services can inspect both claims to apply identity-aware audit logging and authorisation decisions.
If the subject token itself already carries an act claim (e.g. it was issued by Credex in a previous delegation hop), Credex nests the previous actor inside the new actor claim, preserving the entire chain.
In this scenario, the agent workload presents the subject token (e.g. a user OIDC token) as subject_token and its own JWT-SVID as both client_assertion and actor_token.
For example:
POST /tokenContent-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe&client_assertion=<JWT-SVID for agent, aud=https://credex.example.com/token>&subject_token=<OIDC ID token from trusted IdP>&subject_token_type=urn:ietf:params:oauth:token-type:jwt&actor_token=<JWT-SVID for agent, aud=https://credex.example.com/token>&actor_token_type=urn:ietf:params:oauth:token-type:jwt_spiffe&audience=https://api.example.com&scope=data:readand the response would include a token with both a sub claim (the delegated subject) and an act claim (the agent):
{ "iss": "https://credex.example.com", "sub": "user-12345", "act": { "sub": "spiffe://example.com/<agent-identifier>" }, "aud": "https://api.example.com", "exp": 1796000600, "iat": 1796000000}The resulting token would then be used to authenticate to https://api.example.com, which can use both sub and act.sub for authorisation decisions and audit logging.
OIDC to SPIFFE (Preview)
Section titled “OIDC to SPIFFE (Preview)”An OIDC to SPIFFE exchange involves sending a request containing an OIDC ID token signed by a trusted issuer and receiving a JWT-SVID signed by the SPIRE server in response.
This allows non-SPIFFE-native workloads or those running in an environment where SPIFFE Workload API access is not available (e.g. ‘serverless’ workloads such as GitHub Actions or AWS Lambda functions) to obtain SVIDs which are trusted by other SPIFFE-native systems.
An OIDC to SPIFFE exchange requires Credex’s SPIFFE Token Exchange subsystem to be enabled (see Deployment).
The exchange request format is
POST /Content-Type: application/json
{ "InboundToken": "<OIDC JWT string>"}and the response format is
{ "status": "ok", "token": "<JWT-SVID string>"}This functionality is under active development; the request and response formats may change in the near future.
Exchange Policies
Section titled “Exchange Policies”Credex token exchanges are mediated by exchange policies; allowing fine-grained control over which exchanges are permitted and which claims the Credex-issued JWTs may contain.
OAuth Token Exchange
Section titled “OAuth Token Exchange”Every RFC 8693 token exchange is gated by the Credex policy engine. An exchange policy specifies who can exchange what, on whose behalf, and which scopes can be granted in the resulting access token.
If no policy matches an exchange request, the exchange is denied. If a policy source contains zero policies, every exchange is denied.
The Cofide Connect platform provides a centralised source for policy configuration and auditing. Policies are managed by interacting with the Connect API using either the cofidectl CLI, the Terraform provider, or by interacting directly with the Connect gRPC API.
The remainder of this guide will use cofidectl commands to manage exchange policies.
Policy structure
Section titled “Policy structure”A Credex RFC 8693 token exchange policy contains the following fields, which includes both matchers for the exchange request and settings for the outbound token:
| Policy Field | Field Type | Usage |
|---|---|---|
subject_identity | List of string matchers | Matches the sub claim of the subject token |
subject_issuer | List of string matchers | Matches the iss claim of the subject token |
subject_audience | List of string matchers | Matches the aud claim of the subject token (required for ID tokens) |
actor_identity | List of string matchers | Matches the sub / SPIFFE ID of the actor token (delegation flows only) |
actor_issuer | List of string matchers | Matches the iss claim of the actor token |
client_id | List of string matchers | Matches the identity used in the client_assertion (e.g. the calling workload’s SPIFFE ID) |
target_audience | List of string matchers | Matches the audience parameter (if provided by the client) |
outbound_identity | String | Overrides the sub claim of the issued token |
outbound_scopes | List of strings | Caps the scopes granted on the issued token |
external_hooks | List of external hooks | Called in order to enrich the outbound token’s claims (allow policies only) |
action | String | allow or deny |
A string matcher is either an exact string equality check or a glob pattern.
A list of string matchers field matches if any string matcher in the list matches.
Every field of type list of string matchers except actor_identity, actor_issuer, and subject_audience must have at least one matcher.
For required fields, an absent or empty field never matches, and is not a wildcard.
To leave a field unconstrained, set it to a single glob:"*" matcher.
Allow, deny, and resolution order
Section titled “Allow, deny, and resolution order”All policies are evaluated against every request, not just the first match. Resolution proceeds as follows:
- If any matching policy has
action: deny, the exchange is denied immediately. - If any matching
allowpolicy allows the requested scopes, the exchange is permitted. - Otherwise, the exchange is denied.
Deny policies are useful for revoking access for a specific identity without rewriting or reordering the broader allow set.
Actor fields and delegation
Section titled “Actor fields and delegation”The two actor fields (actor_identity and actor_issuer) are the only fields where absence is meaningful:
- If both are absent or empty, the policy only matches exchanges without an actor token (impersonation).
- If either is present, the policy only matches exchanges with a valid actor token (delegation).
This makes it possible to write distinct policies for the impersonation and delegation cases on the same subject identity.
Outbound identity
Section titled “Outbound identity”By default, the sub claim of the issued token matches the sub claim of the subject token.
The optional outbound_identity field can be used to override the sub claim of the issued token.
This can be useful when a downstream resource server or authorization server expects a different subject than the one in the subject token.
If outbound_identity is non-empty, the subject_identity and subject_issuer fields must use exact string matchers only - no glob matchers are allowed.
If several policies match a token exchange request, they must all have the same outbound_identity value, or an error will be returned.
This ensures a predictable mapping of identities.
Policy examples
Section titled “Policy examples”Impersonation
Allow any workload in the payments namespace to exchange a JWT-SVID for an access token targeting the payments API, with payments:read scope:
cofidectl exchange-policy add \ --name policy-1 \ --trust-zone example \ --subject-identity glob:"spiffe://example.org/cluster/example-cluster/ns/payments/sa/*" \ --subject-issuer glob:"*" \ --client-id glob:"spiffe://example.org/cluster/example-cluster/ns/payments/sa/*" \ --target-audience "https://payments.example.com" \ --outbound-scope "payments:read" \ --action allowDelegation
Allow an agent workload to act on behalf of any user authenticated against an external IdP (a delegation flow):
cofidectl exchange-policy add \ --name policy-2 \ --trust-zone example \ --subject-identity glob:"*" \ --subject-issuer "https://login.example.com" \ --actor-identity "spiffe://example.org/ns/agents/sa/booking-agent" \ --actor-issuer glob:"*" \ --client-id "spiffe://example.org/ns/agents/sa/booking-agent" \ --target-audience "https://travel-api.example.com" \ --outbound-scope "bookings:write" \ --action allowThe corresponding exchange request for a delegated access token which is allowed by this policy would be:
POST /tokenContent-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe&client_assertion=<JWT-SVID for booking-agent, aud=https://credex.example.com/token>&subject_token=<external JWT from login.example.com>&subject_token_type=urn:ietf:params:oauth:token-type:jwt&actor_token=<JWT-SVID for booking-agent, aud=https://credex.example.com/token>&actor_token_type=urn:ietf:params:oauth:token-type:jwt_spiffe&audience=https://travel-api.example.com&scope=bookings:writewith a subject JWT containing claims such as:
{ "iss": "https://login.example.com", "sub": "user-12345", "aud": "https://credex.example.com/token", "exp": 1796000600, "iat": 1796000000, "name": "Alice Example"}and actor JWT-SVID claims such as:
{ "sub": "spiffe://example.org/ns/agents/sa/booking-agent", "aud": ["https://credex.example.com/token"], "exp": 1796000300, "iat": 1796000000}The resulting token issued by Credex would include the following claims structure:
{ "iss": "https://credex.example.com", "sub": "user-12345", "act": { "sub": "spiffe://example.org/ns/agents/sa/booking-agent" }, "aud": "https://travel-api.example.com", "exp": 1796000700, "iat": 1796000100}When this Credex-issued token is later sent to the service at https://travel-api.example.com, both the request subject (i.e. user-12345) and the request actor (i.e. the booking-agent) can be used for authorisation decisions and clear audit trails.
External hooks
Section titled “External hooks”An allow policy may optionally specify one or more external hooks. When Credex matches a policy with hooks, it calls each hook endpoint in sequence after evaluating the policy and before minting the token. Each hook can add, remove, or replace custom claims on the outbound token using a JSON Patch (RFC 6902), making it possible to enrich tokens with metadata that Credex does not hold directly.
Request and response
Section titled “Request and response”For each hook, Credex sends a POST request with Content-Type: application/json and a body containing the following fields:
| Field | Required | Description |
|---|---|---|
subject_claims | Yes | Claims from the subject token being exchanged |
actor_claims | No | Claims from the actor token, present only in delegation exchanges |
outbound_claims | Yes | The full set of claims that will appear in the issued token, including sub, aud, and scope |
The hook must return HTTP 200 with a body containing:
| Field | Required | Description |
|---|---|---|
action | No | Must be either allow or deny (default: allow). Denial rejects the token exchange and prevents any subsequent hooks from running for the request. |
action_reason | No | Optional message to complement action. Action reason is returned as a user-facing error message when action is deny. |
claims_patch | No | A JSON Patch (RFC 6902) array to apply to the outbound token’s claims |
Protected claims
Section titled “Protected claims”The following claims are protected and any attempt by a hook to modify them will result in an error and the exchange being rejected: iss, sub, aud, exp, nbf, iat, jti, scope, client_id, act.
Error handling
Section titled “Error handling”If a hook returns a non-200 status code, exceeds its configured timeout (default: 3 seconds), or returns a body that does not conform to the expected schema, Credex rejects the exchange. Credex retries the hook once on a transient network error before failing.
Configuring hooks
Section titled “Configuring hooks”Hooks can be configured using the Terraform provider or the cofidectl CLI.
Each hook accepts the following fields:
Required fields:
| Field | Description |
|---|---|
name | Identifier for the hook, used in error messages |
url | HTTPS endpoint URL of the hook server |
auth | Authentication configuration; exactly one variant must be set (see below) |
Optional fields:
| Field | Description |
|---|---|
description | Human-readable description of the hook’s purpose |
timeout | Per-request timeout; seconds in Terraform, a Go duration string (e.g. 5s) in cofidectl (default: 3 seconds) |
The only supported auth variant is spiffe_mtls, which requires the SPIFFE ID of the hook server.
Credex presents its own X.509-SVID and verifies that the hook server presents a certificate matching that SPIFFE ID.
The hook server must be a SPIFFE workload reachable by Credex.
Example
Section titled “Example”The following Terraform resource defines the impersonation policy above extended with a hook that adds the workload’s team and cost centre to the issued token.
resource "cofide_connect_exchange_policy" "policy_with_hook" { name = "policy-with-hook" trust_zone_id = cofide_connect_trust_zone.example.id action = "ALLOW" subject_identity = [{ glob = "spiffe://example.org/cluster/example-cluster/ns/payments/sa/*" }] subject_issuer = [{ glob = "*" }] client_id = [{ glob = "spiffe://example.org/cluster/example-cluster/ns/payments/sa/*" }] target_audience = [{ exact = "https://payments.example.com" }] outbound_scopes = ["payments:read"] external_hooks = [ { name = "workload-directory" url = "https://directory.internal/hooks/claims" timeout = 5 auth = { spiffe_mtls = { spiffe_id = "spiffe://example.org/ns/directory/sa/claims-hook" } } } ]}When this policy matches an exchange request, Credex calls the hook with:
{ "subject_claims": { "sub": "spiffe://example.org/cluster/example-cluster/ns/payments/sa/checkout", "aud": ["https://credex.example.com/token"], "exp": 1796000300, "iat": 1796000000 }, "outbound_claims": { "sub": "spiffe://example.org/cluster/example-cluster/ns/payments/sa/checkout", "aud": "https://payments.example.com", "scope": "payments:read", "client_id": "spiffe://example.org/cluster/example-cluster/ns/payments/sa/checkout" }}The hook looks up the workload by subject_claims.sub and responds with:
{ "claims_patch": [ { "op": "add", "path": "/department", "value": "engineering" }, { "op": "add", "path": "/cost_centre", "value": "CC-42" } ]}The resulting token issued by Credex includes the enriched claims:
{ "iss": "https://credex.example.com", "sub": "spiffe://example.org/cluster/example-cluster/ns/payments/sa/checkout", "aud": "https://payments.example.com", "scope": "payments:read", "department": "engineering", "cost_centre": "CC-42", "exp": 1796000700, "iat": 1796000100}The credex-policy-hook-reference repository contains an example hook demonstrating the request and response schema required by Credex as well as the SPIFFE mTLS configuration to enable secure communication between Credex and the external hook.
OIDC to SPIFFE (Preview)
Section titled “OIDC to SPIFFE (Preview)”For OIDC to SPIFFE exchanges, Credex enforces which inbound identities may obtain which SPIFFE IDs entirely through SPIRE registration entries, rather than using Cofide ExchangePolicy resources.
A request is granted or denied based solely on whether SPIRE finds a matching registration entry for the selectors Credex presents.
The recommended approach is to use static attestation policies to manage the registration entries.
Each entry binds a specific (issuer, subject) pair to a SPIFFE ID and must be parented to Credex’s SPIFFE ID — this links the entry to Credex’s delegated authority.
For example:
# Create an attestation policy with the Credex selectorscofidectl attestation-policy add static \ --name aws-my-workload \ --selectors credex:oidc:issuer:https://sts.amazonaws.com \ --selectors credex:oidc:claims:sub:arn:aws:sts::012345678910:assumed-role/MyRole/session \ --parent-id-path cluster/<cluster-name>/ns/cofide/sa/cofide-credex \ --spiffe-id-path remote/cloud/aws/my-workload
# Bind the policy to the target trust zonecofidectl attestation-policy-binding add \ --trust-zone local \ --attestation-policy aws-my-workloadReplace <cluster-name> with the name of the cluster on which Credex is running (hint: see cofidectl cluster list).
To allow all subjects from a trusted issuer to obtain the same SPIFFE ID, omit the credex:oidc:claims:sub selector — but this is rarely advisable in production.
If no registration entry matches the presented selectors, SPIRE rejects the request and Credex returns an error to the caller. Adding a trusted issuer to the environment variables is therefore not sufficient on its own: at least one matching SPIRE registration entry must also exist before any exchange will succeed. If the parent ID does not match Credex’s SPIFFE ID, SPIRE will also reject the call even if the selectors match.
Reference
Section titled “Reference”OAuth AS Token Types
Section titled “OAuth AS Token Types”The Credex OAuth Token Exchange subsystem accepts various token types for each request parameter.
The client_assertion_type parameter must be one of:
urn:ietf:params:oauth:client-assertion-type:jwt-spiffe- SPIFFE client authenticationurn:ietf:params:oauth:client-assertion-type:jwt-bearer- RFC 7523
The subject_token_type must be one of:
urn:ietf:params:oauth:token-type:jwt- a JWT from an external trusted issuer, similar to an RFC 7523 JWT Bearer Authorization Grant.urn:ietf:params:oauth:token-type:jwt_spiffe- a SPIFFE JWT-SVID.urn:ietf:params:oauth:token-type:access_token- an OAuth access token previously issued by Credex.urn:ietf:params:oauth:token-type:id_token- an OIDC ID token from an external trusted issuer.
The actor_token_type must be one of:
urn:ietf:params:oauth:token-type:jwt_spiffe- a SPIFFE JWT-SVID.urn:ietf:params:oauth:token-type:access_token- an OAuth access token previously issued by Credex.
RFC 8693 also supports an optional requested_token_type parameter; however, Credex currently only supports a value of urn:ietf:params:oauth:token-type:access_token if the parameter is included (though this may change in future).
Similarly, the issued_token_type field in the response will be set to urn:ietf:params:oauth:token-type:access_token in all responses.
Token Audience Requirements
Section titled “Token Audience Requirements”The required aud claim varies depending on the role a token plays in the exchange request.
The URN prefixes have been removed from the following tables for brevity.
Client assertion
Section titled “Client assertion”The audience requirement for the client_assertion depends on its type:
client_assertion_type | Required aud |
|---|---|
jwt-spiffe (JWT-SVID) | Credex token endpoint URL |
jwt-bearer (external JWT) | Credex token endpoint URL, or one of the allowed audiences configured for the issuer via TRUSTED_ISSUER_ALLOWED_AUDIENCES_n |
For jwt-bearer, Credex follows the audience requirements in RFC 7523 for JWT bearer client authentication which state that the audience must identify the authorization server (i.e. Credex).
Since some OAuth AS implementations do not allow requesting a specific audience, the TRUSTED_ISSUER_ALLOWED_AUDIENCES_n configuration may be used as a workaround for accepting audiences other than the Credex token endpoint URL.
Subject token
Section titled “Subject token”The audience requirement for the subject_token depends on its type:
subject_token_type | Required aud |
|---|---|
access_token (Credex-issued) | Any |
jwt (external JWT) | Credex token endpoint URL, or one of the allowed audiences configured for the issuer via TRUSTED_ISSUER_ALLOWED_AUDIENCES_n |
id_token (OIDC ID token) | Credex token endpoint URL, or the subject_audience policy field |
jwt_spiffe (JWT-SVID) | Any |
The distinction between jwt and id_token is subtle.
For jwt, Credex follows the audience requirements in RFC 7523 for JWT bearer authorization grants: the audience must identify the authorization server.
The per-trusted issuer allowed audiences configuration allows for accepting audiences other than the AS token endpoint URL.
For id_token, Credex follows the audience requirements in the OpenID Connect Core 1.0 specification: the audience identifies the Relying Party that requested the ID token from the issuer (typically an IdP).
This is likely to vary for each client, so a policy-driven approach is used.
Note that for clients using an ID token both as a subject ID token and JWT bearer client assertion, both TRUSTED_ISSUER_ALLOWED_AUDIENCES_n and subject_audience will need to be set if the audience is not the AS token endpoint URL.
Actor token
Section titled “Actor token”Actor tokens must always be bound to the Credex token endpoint:
actor_token_type | Required aud |
|---|---|
access_token (AS-issued) | Credex token endpoint URL |
jwt_spiffe (JWT-SVID) | Credex token endpoint URL |
© 2026 Cofide Limited. All rights reserved.