Skip to main content

External Identity Providers

Use an ExternalIdentityProvider when your own application calls Mission Control's API on behalf of its users, for example when you embed Mission Control in your product. It isn't for signing people in to the Mission Control UI. For that, see Single Sign-On.

Your application signs a JWT for each of its users with its own issuer, and sends it with each request. Mission Control treats the request as coming from that user: a person named <provider>:<username>, who belongs to the teams the provider maps them into.

A provider answers three questions, in this order:

QuestionAnswered byResult
Is this token real, and is it for us?VerificationThe token is accepted or rejected
Who is this person?identityA person
Which teams are they in?identity.teamsTeam memberships, limited to an allowlist

What the person may do is never decided by the provider. It comes from RoleBindings that select the person or their teams, and RoleBindings never read claims.

ExternalIdentityProviders are managed through Kubernetes only.

Fields​

FieldRequiredDefaultDescription
spec.issuerYesMust exactly match the token's iss claim. Can't be changed
spec.audienceYesMust be one of the token's aud claims, usually the Mission Control URL
spec.jwksURLNojwks_uri of the issuer's OpenID configurationWhere the issuer's signing keys are fetched from
spec.algorithmsNoRS256, ES256Signature algorithms accepted
spec.maxTokenAgeNo5mLongest lifetime (exp - iat) a token may have
spec.identity.usernameYesCEL expression returning the person's username
spec.identity.nameNoThe usernameCEL expression returning the person's display name
spec.identity.emailNoCEL expression returning an email, shown on the person only
spec.identity.teams.mapperIf teams is setCEL expression returning a list of team names
spec.identity.teams.allowedIf teams is setThe only teams this provider may add anyone to. At least one
spec.disabledNofalseRejects every token from this provider, without removing it

For example, a provider named oipa, with issuer https://auth.oipa.example.com, identity.username set to claims.user.id, and a team mapper with allowed: [acme-operator, acme-viewer], signs in a token whose user.id claim is u-4821 as the person oipa:u-4821.

How a request is signed in​

Your application sends the user's token in the Authorization: Bearer <token> header of every request. There's no session or cookie.

Mission Control looks at the token's iss claim. If it matches a provider's issuer, the token must pass that provider's checks, or the request is refused with 401 Unauthorized. Tokens from other issuers are handled by Mission Control's normal sign-in, as if no provider existed.

Don't set issuer to the issuer Mission Control's own sign-in uses, such as its Clerk issuer. Such a provider shows Ready=False and is ignored.

Verification​

A token is accepted only when these checks pass:

CheckRule
ProviderThe provider isn't disabled
SignatureSigned with one of algorithms, by a key from the provider's signing keys
issExactly issuer
audContains audience
expPresent, and not in the past
iatPresent, and not in the future
nbfIf present, not in the future
Lifetimeexp - iat is at most maxTokenAge
Usernameidentity.username returns a non-empty string without *

Time checks tolerate 30 seconds of clock skew. Mission Control can't revoke these tokens, so maxTokenAge keeps them short-lived. An application that needs longer sessions issues new tokens, not longer ones.

Every token that passes these checks is trusted, so the issuer must be dedicated to your application. Shared issuers that sign tokens for many unrelated applications, such as Google, GitHub Actions or multi-tenant Microsoft Entra, aren't supported.

Algorithms and keys​

  • Only asymmetric algorithms such as RS256, ES256 and EdDSA are accepted. Shared-secret algorithms such as HS256 are rejected.
  • issuer and jwksURL must use https, except for localhost during development.
  • Mission Control fetches the signing keys from jwksURL, or from your issuer's /.well-known/openid-configuration, and picks up rotated keys automatically.
All accepted algorithms and key fetching details
  • Accepted algorithms: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512 and EdDSA. none is never accepted.
  • A discovered jwks_uri must use https too. Redirects are followed at most 5 times, and never from https to http.
  • Keys are fetched on the provider's first token, cached, and refreshed in the background. If they can't be fetched, the request fails with 500 Internal Server Error, and the next token retries.

Identity​

Each user of a provider is a person, created on their first request. Their key is <provider name>:<username>, such as oipa:u-4821, and that's how RoleBindings name them.

username, name and email are CEL expressions over claims, which holds every claim of the verified token:

identity.name
[claims.user.first_name,
has(claims.user.middle_name) ? claims.user.middle_name : "",
claims.user.last_name].filter(s, s != "").join(" ")
  • If username fails, or doesn't return a non-empty string, the token is rejected with 401. Convert a numeric user ID with string().
  • A username containing * is rejected, so it can't be confused with <provider>:*.
  • If name or email fails, the person keeps the stored value.
  • The username must be unique and stable in your application. A username that changes makes a new person, without the old one's teams or bindings.
  • These people are separate from Mission Control's own users. An external user whose email is alice@example.com gets none of the Mission Control user alice@example.com's access. Mission Control never verifies, matches on, or sends anything to that email.
  • Wherever a person is shown, such as in approvals and team members, a provider's person is shown with their key.

Teams​

The person's teams from the provider become exactly the teams the mapper returns that are also in allowed:

identity.teams.mapper
has(claims.org.roles) ? claims.org.roles.map(r, "acme-" + r) : []
  • Allowed teams only. A returned team that isn't in allowed is skipped and logged. Including the shipped admins team in allowed makes the provider report a warning, since your application's claims then decide who is a Mission Control admin.
  • Existing teams only. The provider never creates teams. An allowed entry naming a team that doesn't exist is reported in the provider's status.
  • Kept in sync both ways. The person joins returned teams and leaves teams this provider added them to that it no longer returns.
  • Only its own memberships. The provider never removes a membership an admin added. A membership an admin removes comes back on the next sync if the mapper still returns it. To keep a user out of a team, change allowed or the claims.
  • On every request. The mapper runs before each request is checked, and memberships are written only when they change.
  • A failing mapper refuses the request with 401 and a body saying that team mapping failed. The stored memberships stay as they are.
  • Removing a team from allowed, or removing identity.teams, removes this provider's memberships of those teams at once.
  • Without teams, the provider adds no one to any team. Its users get only what RoleBindings grant them by name.

These memberships work like any other team membership, including for playbook approvers and on the Subjects page, until the person's next request changes them. Team Mapping covers team mappers in general.

How changes take effect​

ChangeEffect
A valid provider is appliedTakes effect at once
A provider becomes invalidReady=False. Its tokens get 401, and its people keep their memberships
disabled: trueIts tokens get 401, and everything else is kept
issuer changesRejected. A provider for another issuer is a new provider
Teams are added to allowed, or the mapper changesApply on each person's next request
The provider is deletedIts people are retired: kept for audit, removed from every team, and never signed in as again. See the warning below

issuer must be unique across all providers, and name across all namespaces. Of providers that share either, the one with the earliest creation timestamp wins and the others are Ready=False.

A provider is rejected when its issuer uses http, it uses HS256, maxTokenAge isn't a positive duration, audience or identity.username is missing, or a team mapper has no allowed. A provider whose CEL expression doesn't compile is stored as Ready=False with reason Invalid.

warning

Deleting a provider doesn't delete the RoleBindings that name its users, such as <provider>:u-4821 or <provider>:*. A new provider created later with the same name gets those bindings. Delete them along with the provider.

Limitations​

  • Only JWT bearer tokens are supported: no opaque tokens, introspection or userinfo lookups.
  • Tokens can't be revoked before they expire. Disable or delete the provider to cut off all its users at once.
  • Access belongs to the person, not the token. Your application can't give the same user different access per request, such as by switching a tenant claim between tokens.
  • Users of a provider can't create personal access tokens.