Skip to main content
Version: 1.2

Configure an IdentityProvider

Suggested reading

Read the Identity overview first for what Hub identifies and how tokens reach it. To register your first provider as part of a Helm install, see Installing Hub, which generates a provider from a handful of values.

An IdentityProvider allows you to configure which identity systems the Hub can trust. The IdentityProvider represents one OIDC issuer in the iam.hub.upbound.io API group. It carries both the OAuth2 redirect parameters for the login flow and the token-validation rules Hub applies to every bearer token.

A minimal provider​

A complete IdentityProvider for a standards-compliant issuer that humans log in through looks like this:

apiVersion: iam.hub.upbound.io/v1beta1
kind: IdentityProvider
metadata:
name: entra
spec:
redirect:
browserLogin: true
clientSecret: "<client-secret>" # encrypted at rest; reads return "***"
scopes:
- openid
- email
- profile
validation:
issuer:
url: https://login.microsoftonline.com/<tenant-id>/v2.0
audiences:
- <client-id>
claimMappings:
username:
# A user with email alice@example.com becomes "entra:alice@example.com".
claim: email
groups:
# A group value "platform-eng" becomes the Hub group "entra:platform-eng".
claim: groups
extra:
- key: authentication.hub.upbound.io/email
valueExpression: claims.email
claimValidationRules:
- expression: "claims.email_verified == true"
message: "email must be verified"
userValidationRules:
- expression: "user.username.endsWith('@example.com')"
message: "email domain not permitted"

Apply it through the API, or add it to hub-core.bootstrap.files for GitOps-style management. hub-auth reconciles IdentityProviders from that directory. See Keep credentials in a Secret to avoid putting the client secret in the file.

Key knobs​

  • userInfoPrefix. Prepended to every value Hub reads from the username, group, and extras claims. It must equal the provider's metadata.name followed by a colon (entra: for a provider named entra), and Hub fills in that value when you omit it. Choose the provider name with this in mind. The prefix may not overlap the reserved system: or upbound: namespaces, which is what stops a provider from claiming system:masters.
  • claimMappings.username and claimMappings.groups. Select which claims in the token become the Hub username and group list. username defaults to the sub claim; set it explicitly when you want a readable username such as email. groups is optional but recommended. Without it you can only bind roles to individual users.
  • claimValidationRules and userValidationRules. CEL expressions Hub evaluates on every token. Failed rules reject the token. Common uses: require email_verified == true, restrict to a single email domain, or block reserved group prefixes.
  • redirect.browserLogin. At most one IdentityProvider may set this to true. That provider drives the Hub UI login redirect when you register multiple IdPs.
  • redirect.prompt. The OIDC prompt parameter Hub sends on the login redirect. When unset, Hub sends prompt=consent, so providers that issue a refresh token only at first consent, such as Google, keep issuing one. Set it to "" to omit the parameter, which stops a provider with consent already granted (for example Entra ID with org-wide admin consent) from showing a consent screen on every login. login, select_account, or a space-separated combination are sent as-is. Hub rejects none.

Issuer options​

validation.issuer takes these optional fields alongside url and audiences:

FieldPurpose
backendIssuerURLIn-cluster base URL of the provider. Hub rewrites any request that starts with the public issuer URL (discovery, JWKS, and token endpoint calls) to use this URL instead. Use it when the issuer URL in tokens isn't reachable from inside the cluster.
certificateAuthorityPEM CA bundle for TLS connections to the discovery and JWKS endpoints. Defaults to the system root pool.
jwksInline JSON Web Key Set. Hub verifies tokens against these keys and skips discovery and network JWKS fetching. url must still match the iss claim.
audienceMatchPolicyHow audiences is matched against the token's aud claim: MatchAny (default) or MatchAllClaimsInTokenAudience.

Fields you can't change later​

These fields are immutable, so getting them wrong means deleting and recreating the provider:

  • validation.userInfoPrefix. Every role binding written against the old prefix stops matching, so a change is a migration. See Multiple providers.
  • validation.issuer.url. Must equal the iss claim in issued tokens verbatim, including scheme, host, and path. Omit any trailing slash.

Hub also reserves certain metadata.name values: the names upbound, ctp, and space, and anything starting with upbound-, ctp-, or space-, for providers it creates itself. Unrelated names that merely begin with the same letters, such as spacex, are fine.

Defaults worth knowing​

  • redirect.scopes is required whenever you set redirect, and must include openid. Hub rejects a provider without it. ["openid", "email", "profile"] is the usual set.
  • A sub claim is always required. Hub prepends a claim-validation rule of its own demanding a non-empty string sub, regardless of what you map the username to. Hub rejects a token without one before your own rules run.

The redirect URI​

The IdentityProvider doesn't carry a redirect URI. hub-auth, the service that runs the login flow, composes the callback from its externally reachable base URL (<base-url>/oidc/callback), so the URI you register with your provider has to match that host verbatim. On a Helm install the base URL is hub-auth.auth.authExternalURL, falling back to hub-auth.auth.externalURL when that's empty. The install guide sets hub-auth.auth.externalURL to the same URL as global.hubCoreExternalUrl. See Installing Hub.

Keep credentials in a Secret​

A provider in the bootstrap directory can read its client ID, client secret, and issuer URL from a Kubernetes Secret instead of carrying them inline. Add an annotation whose value is <secret-name>/<key> for each field:

AnnotationFills
bootstrap.hub.upbound.io/client-id-fromspec.validation.issuer.audiences[0], the client ID the redirect sends. Any further audiences stay as written.
bootstrap.hub.upbound.io/client-secret-fromspec.redirect.clientSecret
bootstrap.hub.upbound.io/issuer-url-fromspec.validation.issuer.url
apiVersion: iam.hub.upbound.io/v1beta1
kind: IdentityProvider
metadata:
name: entra
annotations:
bootstrap.hub.upbound.io/client-id-from: entra-oidc/client-id
bootstrap.hub.upbound.io/client-secret-from: entra-oidc/client-secret
spec:
# ...as above, without clientSecret or the first audience

hub-auth mounts only the Secrets you list in hub-auth.auth.bootstrap.credentialSecretRefs, so add each one there. Login fails for a provider whose Secret isn't listed. hub-auth can read every key in a listed Secret, so keep those Secrets limited to OIDC values. Rotated values take effect within a few minutes.

What browser login does​

Registering the provider above wires up the flow below. Understanding it helps for two reasons: it shows why the redirect URI has to match, and it shows where the IdP stays in the loop after the initial sign-in.

The following properties of this flow matter operationally:

  • Neither the IdP's ID token nor its refresh token reaches the browser. They live on the session row. The browser holds only hub_sid, an opaque HMAC-signed handle, and hub_token, the short-lived Hub JWT. Both are HttpOnly, so page JavaScript never sees either.
  • Login and token exchange run in a separate service. hub-auth (port 8444 by default) accepts session cookies and IdP tokens. hub-core, which serves the main API, accepts only Hub-signed JWTs. That keeps the fast verification path free of database lookups.
  • The IdP stays in the loop. Hub extends a session by redeeming the stored refresh token, not by minting a long-lived credential of its own. See Revocation.

Logging out at /oidc/logout deletes the session, clears both cookies, and, when the IdP advertises an end_session_endpoint, redirects through it with id_token_hint, so the IdP clears its own SSO cookies instead of signing you back in without asking.

Multiple providers​

You can register any number of IdentityProvider resources. The token-exchange endpoint accepts every provider's tokens, so a second provider is the normal way to admit CI and workload identities alongside your human IdP.

Hub enforces uniqueness rules across providers, including one browser-login holder, no shared issuer URLs, and no overlapping userInfoPrefix values, and moving browser login between providers takes a specific sequence. See Multiple providers.

Sample identity providers​

The fields above mean the same thing for every provider. What differs is how each one emits group claims and what its issuer URL looks like, so a complete worked IdentityProvider (app registration, issuer URL format, group-claim setup, and directory configuration where it applies) lives on its own page:

A provider that isn't listed still works as long as it's OIDC-compliant: a stable issuer URL publishing .well-known/openid-configuration, the authorization code flow, and a claim carrying group membership. The Keycloak and Okta pages are the closest templates to start from.