Identity providers
The identityProviders block lists the identity providers that vouch for users. Each entry describes how Defakto verifies and reads the provider's tokens, and optionally how people sign in through the provider.
An identity provider is used in two ways:
- Workloads present the provider's tokens. A workload exchanges a user's ID token or access token for an on-behalf-of token, or a user's access token for a Transaction Token. The
idTokensandaccessTokensblocks describe how Defakto accepts these tokens. - People sign in through the provider. An OAuth client, such as an MCP client, sends a person to sign in, and Defakto redirects the person to the provider. The
loginblock describes this.
Example
identityProviders:
- name: corp-idp
issuer: https://idp.example.com
idTokens:
audiences: [web-app]
claimValidationRules:
- claim: email_verified
requiredValue: true
accessTokens:
audiences: [https://api.example.com]
login:
clientID: <client-id-at-your-identity-provider>
clientAuth:
method: private_key_jwt
scopes: [openid, email]
Fields
An entry has a name, an issuer, and at least one of idTokens, accessTokens, and login.
| Field | Required | Description | Example |
|---|---|---|---|
name | Yes | A name for the entry. Token policy rules refer to the entry by this name, in match.user.identityProvider. A DNS label, unique across all named entries in the section. | corp-idp |
issuer | Yes | The provider's issuer URL. Tokens from the provider must carry it as iss. Each entry must have a different issuer. Defakto reads the provider's discovery document from <issuer>/.well-known/openid-configuration, and verifies tokens with the signing keys it lists. The discovery document's issuer must equal this value. | https://idp.example.com |
idTokens | At least one of idTokens, accessTokens, and login | How Defakto accepts the provider's OpenID Connect ID tokens. See Token type blocks. | |
accessTokens | At least one of idTokens, accessTokens, and login | How Defakto accepts the provider's access tokens. See Token type blocks. | |
login | At least one of idTokens, accessTokens, and login | How people sign in through the provider. Only one provider can have it. See login. |
Token type blocks
The idTokens and accessTokens blocks have the same fields, and each applies to one kind of token. Configure a block only for each kind of token your workloads will exchange. For example, if your workloads exchange only ID tokens, leave out accessTokens.
| Field | Required | Description |
|---|---|---|
audiences | Yes, for tokens that workloads present | Who the tokens must be issued for, as named in their aud claim. Not used for sign-in, which always checks the ID token's audience against login.clientID. |
claimValidationRules | No | Rules that every token of this kind must satisfy. See claimValidationRules. |
Access tokens must be JWTs with an aud claim.
How a presented token is matched
When a workload presents a token, Defakto uses the request's subject_token_type to choose the block: urn:ietf:params:oauth:token-type:id_token selects idTokens, and urn:ietf:params:oauth:token-type:access_token selects accessTokens. The token's iss must match exactly one entry for that kind of token, and its JWT typ header must be consistent with the kind. Defakto rejects a token that matches no entry.
claimValidationRules
Rules that every token of this kind must satisfy, in addition to the issuer and audience checks. All rules must hold. Use them to narrow a provider's tokens to your organization, or to require a property of the token. On idTokens, the rules also apply to the ID tokens Defakto receives when people sign in.
| Field | Required | Description | Example |
|---|---|---|---|
claim | Yes | The name of a top-level claim. iss and aud are rejected, because issuer and audiences check them. | email_verified |
requiredValue | Yes | The value the claim must have, compared exactly, including its JSON type. A string, number, or boolean. true doesn't match "true". A token that doesn't have the claim fails the rule. | true |
Make sure the provider includes each claim that a rule checks. When people sign in, some providers include a claim in the ID token only when Defakto requests a matching scope, such as email for email_verified. Add that scope to login.scopes.
User identifier
Defakto identifies the user by the sub claim in the provider's tokens, and writes it into the tokens it issues for the user:
| Token Defakto issues | Where the user appears |
|---|---|
| On-behalf-of token | obo.sub. The top-level sub is the acting workload. |
| Transaction Token for a user | sub |
| Access token from a sign-in | sub |
The value is set at the first exchange, and carried unchanged through later re-exchanges.
login
How people sign in through the provider, when an OAuth client such as an MCP client asks for an access token for a person. Only one identity provider can have login. People sign in through that provider in every flow that needs a person to sign in.
| Field | Required | Description | Example |
|---|---|---|---|
clientID | Yes | Defakto's client ID in the provider, from the application you register for Defakto. | <client-id-at-your-identity-provider> |
clientAuth | Yes | How Defakto authenticates to the provider. See clientAuth. | method: private_key_jwt |
scopes | No. Default [openid, profile, email]. | The scopes Defakto requests from the provider to sign the person in. Must include openid. Add the scopes your provider needs to include the claims that claimValidationRules check on idTokens. | [openid, email] |
Register <publicBaseURL>/oauth/upstream-callback as a redirect URI in the provider's application.
The ID token from sign-in
Sign-in uses OpenID Connect. Defakto sends the person to the provider, and when they've signed in, the provider returns an ID token that says who they are. Defakto verifies the ID token and reads the person's identifier from its sub claim.
For most providers, that's all sign-in needs, so most providers with login have no idTokens block. Add one in these cases:
- Your identity provider requires a rule. Some identity providers, such as Google, use one issuer for many organizations. For these identity providers, Defakto requires a rule that accepts only people from your organization, such as a rule on your Google Workspace domain. For the providers and the rule each one requires, see Issuer-specific validation rules.
- You want to add your own checks on the ID token, with
claimValidationRules. For example, to require a verified email address.
This provider requires a verified email address for sign-in. It requests the email scope, so that the provider includes email_verified in the ID token:
identityProviders:
- name: corp-idp
issuer: https://idp.example.com
login:
clientID: <client-id-at-your-identity-provider>
clientAuth:
method: private_key_jwt
scopes: [openid, email]
idTokens:
claimValidationRules:
- claim: email_verified
requiredValue: true
clientAuth
How Defakto authenticates to another authorization server: to an identity provider in login, or to an external service in grant.clientCredentials. Some identity providers need specific clientAuth settings. See provider guides.
| Field | Required | Description | Example |
|---|---|---|---|
method | Yes | private_key_jwt or client_secret_basic. See below. | private_key_jwt |
audience | No. Allowed only with private_key_jwt. | The audience of Defakto's signed JWT. By default, it's the authorization server's token endpoint, found in its discovery metadata, as OpenID Connect specifies, and most providers accept it. Set audience only if a provider guide gives a value for your provider, or your provider requires a different audience. | https://auth.example.net |
secretRef.name | Required with client_secret_basic | The name of the Kubernetes Secret that holds the client secret. | vendor-api |
secretRef.key | Required with client_secret_basic | The key in the Secret that holds the client secret. | clientSecret |
private_key_jwt
Defakto signs a JWT (RFC 7523) with the trust domain's OAuth signing key, and the authorization server verifies it with Defakto's published keys. No secret is stored. Register your trust domain's OAuth JWKS Endpoint as the client's JWKS URL in the authorization server, so that it picks up new keys when Defakto rotates them. Find the OAuth JWKS Endpoint in the Defakto console, under your trust domain's Settings > Trust domain URLs, or in the output of defakto trust-domain info <trust-domain>.
With the default audience, or a URL on the authorization server's own host, Defakto's JWT has the client ID as both iss and sub. With any other audience, such as api://AzureADTokenExchange, it has your trust domain's OAuth issuer as iss and urn:defakto:oauth-client:<clientID> as sub, as federated credentials expect.
client_secret_basic
Defakto sends a client secret, read from a Kubernetes Secret that you create and manage in the namespace where your Trust Domain Servers run. If you run several Trust Domain Server deployments, create the Secret for each one. Each Trust Domain Server reads the Secret each time it uses it, so a changed secret takes effect without a restart. The Secret must have:
- The label
defakto.security/credential: oauth-server - The annotation
defakto.security/issuer, set to the issuer the secret authenticates to: the identity provider'sissuerforlogin, orauthorizationServer.issuerfor an external service
apiVersion: v1
kind: Secret
metadata:
name: vendor-api
namespace: <Trust Domain Server namespace>
labels:
defakto.security/credential: oauth-server
annotations:
defakto.security/issuer: https://auth.example.net
type: Opaque
stringData:
clientSecret: <client-secret>
Trust Domain Servers can read the Secret only when oauthClientSecrets is enabled in your Helm values. List the name of every Secret that holds a client secret. Without names, Trust Domain Servers can read every Secret in their namespace:
trustDomainDeployment:
deployment:
oauthClientSecrets:
enabled: true
names: [vendor-api]
If the Secret is missing, isn't labeled, names a different issuer, or isn't readable because oauthClientSecrets isn't enabled or doesn't list it, requests that need it fail, and the audit event records secret_unusable.
Issuer-specific validation rules
Defakto trusts an identity provider by its issuer. For most providers, the issuer belongs to one organization, so a token with that issuer comes from your organization's directory. Some providers use one issuer for every organization they serve. For those providers, a matching issuer only proves that someone has an account at the provider, so Defakto requires a claim validation rule that pins your organization.
Issuers that require a rule. An entry with one of these issuers is rejected unless it has a claimValidationRules rule for the listed claim, with an exact value:
| Provider | Issuer | Required rule |
|---|---|---|
https://accounts.google.com | hd set to your Google Workspace domain. Personal Google accounts and Google Cloud service accounts share this issuer, but their tokens have no hd value, so they aren't allowed. | |
| Slack (Sign in with Slack) | https://slack.com | https://slack.com/team_id set to your Slack workspace ID |
| GitLab.com CI/CD | https://gitlab.com | namespace_id set to the ID of your group |
| GitHub Actions | https://token.actions.githubusercontent.com | repository_owner_id set to your organization's ID |
| Buildkite | https://agent.buildkite.com | organization_id |
| HCP Terraform | https://app.terraform.io | terraform_organization_id |
| Pulumi Cloud | https://api.pulumi.com/oidc | org |
| Vercel, global issuer mode | https://oidc.vercel.com | owner_id set to your team's ID. Team issuer mode needs no rule. |
| Codefresh | https://oidc.codefresh.io | account_id |
| Scalr | https://scalr.io | scalr_account_id |
| Ona (formerly Gitpod) | https://app.gitpod.io | organization_id |
An entry that has only login needs the rule too. Put it in an idTokens block, because Defakto checks the ID token from sign-in with that block's rules.
Issuers that aren't accepted. These issuers serve many organizations, and their tokens don't identify the organization in a way that a rule can check:
| Provider | Issuer | Instead |
|---|---|---|
| Salesforce | https://login.salesforce.com, https://test.salesforce.com | Use your organization's My Domain issuer |
| Microsoft personal accounts | https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0 | Use your organization's Microsoft Entra tenant issuer |
| Apple (Sign in with Apple) | https://appleid.apple.com | |
https://www.linkedin.com/oauth | ||
| GitLab.com sign-in | https://gitlab.com | Use a GitLab Self-Managed or GitLab Dedicated issuer. GitLab.com CI/CD shares this issuer, and is accepted with its required rule. |
| Amazon Cognito identity pools | https://cognito-identity.amazonaws.com | Use an Amazon Cognito user pool issuer |
| sandboxes.cloud | https://sandboxes.cloud | |
| Upbound | https://proidc.upbound.io | |
| DVC Studio | https://studio.datachain.ai/api | |
| Shisho Cloud | https://tokens.cloud.shisho.dev | |
GitHub (vstoken) | https://vstoken.actions.githubusercontent.com | |
| GitHub audit log streaming | https://oidc-configuration.audit-log.githubusercontent.com | |
| IBM Turbonomic SaaS | https://rh-oidc.s3.us-east-1.amazonaws.com/<id>, https://oidc.op1.openshiftapps.com/<id> | |
| Microsoft Sentinel | https://sts.windows.net/33e01921-4d64-4f8c-a055-5bdaffd5e33d |
Validation
When you apply the configuration, Defakto also rejects:
- Two entries with the same issuer
loginon more than one entry- Removing a token type block or
loginfrom an entry while a token policy rule refers to it withvia - Removing an entry while a token policy rule refers to it