IdentityProvider
Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated
An IdentityProvider declares an external OpenID Connect provider, such as Google or
Entra ID, or GitHub, that an organization's users may sign in and sign up
with. The operator reads
the client secret from the Secret spec.clientSecretRef names and sends it with the spec
to the server's gRPC AdminService (UpsertIdentityProvider). The server stores the
secret sealed and never returns it.
| Property | Value |
|---|---|
| API group / version | nauthera.io/v1alpha1 |
| Kind | IdentityProvider |
| Plural / short name | identityproviders / idp |
| Scope | Namespaced |
| Definition | deploy/ in nauthera-server |
A provider is offered to no client by default. Each OIDCClient
opts in by listing the provider's slug under spec.identityProviders.
Example
Create the client at the provider first, then put the secret it issued into a Secret
beside the resource. Use --from-literal or printf '%s', not echo, so the value has
no trailing newline:
kubectl -n nauthera create secret generic google-oidc \
--from-literal=client-secret=<the secret Google issued>apiVersion: nauthera.io/v1alpha1
kind: IdentityProvider
metadata:
name: google
namespace: nauthera
spec:
slug: google
displayName: Google
providerIssuer: https://accounts.google.com
clientId: 1234567890-example.apps.googleusercontent.com
clientSecretRef:
name: google-oidc
key: client-secret # the default
scopes: [openid, email, profile]
trustEmail: true
allowedEmailDomains: [example.com] # optional; empty admits every domain
deletionPolicy: RetainOnce the resource is Ready, register status.redirectUri as an authorized redirect URI
of the client at the provider. It is
<organization issuer>/.
GitHub
GitHub issues no ID token, so it is not an OpenID Connect provider. With
providerIssuer: the server signs in through GitHub's
OAuth 2.0 flow instead (with state and PKCE) and reads the user from GitHub's REST API:
the numeric user id is the subject, the name (or login) the name, and the primary address
the email, with GitHub's own verified flag, so trustEmail and allowedEmailDomains work
as for any provider.
Register a GitHub App, which needs the account permission Email addresses (read), or an
OAuth App, with status.redirectUri as its callback URL. Scopes default to
read:user user:email; a GitHub App ignores them. GitHub reports nothing about how its
user signed in, so trustMfa is refused; use spareSecondFactor if GitHub sign-ins should
not meet the server's second factor. The server needs egress to github.com and
api.github.com.
apiVersion: nauthera.io/v1alpha1
kind: IdentityProvider
metadata:
name: github
namespace: nauthera
spec:
displayName: GitHub
providerIssuer: https://github.com/login/oauth
clientId: Iv23liExample
clientSecretRef:
name: github-oauth
trustEmail: trueA refused code, a token the API refuses or a user without an id fails the sign-in like an
unreachable provider: the audit record has the cause upstream_unavailable with GitHub's
status and error code.
Offering it to a client
A client then offers the provider in its own spec:
apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
name: example-webapp
namespace: nauthera
spec:
clientId: example-webapp
# …
identityProviders:
- slug: google
allowSignIn: true
allowSignUp: truekubectl get identityproviders shows the slug, the provider's issuer, the phase and the
Ready condition's status and reason; -o wide adds the message.
Before you apply one
The operator's identity needs the identity_providers operation, and the organization
and namespace, in its server. entry. The Helm chart's default
entry (certManager.) grants it in the main organization from
every namespace; add a tenant's slug for providers there.
nauthera-cli create identity-provider registers a provider without Kubernetes (see
nauthera-cli).
Trust settings
trustEmailsays whether the provider'semail_verifiedclaim proves that its user controls the address. That is what lets a sign-in through the provider link to an existing account holding the address. Leave itfalsefor a provider that lets its users claim an address they do not control.allowedEmailDomainsadmits only accounts whose verified address is in one of the listed domains, matched exactly (a subdomain is its own entry), on sign-in and sign-up alike, accounts already linked included. Any other account is sent back to the sign-in page. It needstrustEmail: true, since only a trusted provider's verified address proves a domain; without it the server refuses the resource.trustMfalets the provider's own multi-factor sign-in stand in for the server's second factor. A sign-in whose ID token has anamrholdingmfa(RFC 8176) is not asked for, or made to set up, a second factor, and its session carriesamrmfaandacraal2. For a provider that reports MFA inacrinstead, list those values inmfaAcrValues, which needstrustMfa: true. A token with neither gets the server's own second factor. Theauth.sign_in.completedaudit record names the claim that vouched inupstream_mfa. Turn it on only for a provider that enforces the MFA it reports: Entra ID, Okta and Auth0 sendamr; Google sends neither claim, and GitHub cannot be trusted for it.spareSecondFactorlets a sign-in through the provider skip the server's second factor when the provider did not vouch for one: it is neither challenged nor sent to enroll, even where the deployment, the organization, the client or a rule requires a factor. It is for a provider whose users secure their own accounts but that reports nothing about how they signed in, such as Google or GitHub. Such a sign-in is single-factor: its session and tokens carryamrfedand nomfa, and itsauth.sign_in.completedrecord saysmfa_. A sign-in the provider does vouch for underspared: identity_ provider trustMfastill getsmfaandaal2, and a password sign-in in the same organization still needs its factor.
Sign-up through a provider needs the client's allowSignUp and the deployment's
auth.; see Sign up.
What the operator does
- Registers and updates the provider, sending the spec and the client secret for every generation the server does not hold yet, and again whenever the Secret changes. After a restart the operator sends each provider once more.
- Keeps the secret in its Secret. The resource never holds it. The server reports
only a non-reversible fingerprint, in
status.secretFingerprint. Editing the Secret sends the new secret. - Waits for the Secret. A Secret or key that is not there leaves the resource in
ErrorunderSecretNotFoundorSecretKeyMissing, naming it. A value that begins or ends with whitespace, such as the newlineecholeaves, isErrorunderSecretPadded: no provider issues one, and every provider refuses it. Nothing is sent until the Secret is fixed, and the resource is reconciled when it changes. - Fixes the slug and organization.
spec.slug(the resource's name when unset) cannot be changed after creation, since the slug is part of the redirect URI registered at the provider. The organization follows from the resource's namespace (see Which organization a resource configures);spec.organizationnames it where several organizations admit the namespace, and once set cannot be changed.
Deleting a resource
spec.deletionPolicy decides, and defaults to Retain:
Retainleaves the provider registered, so deleting a namespace cannot take a sign-in method away from the organization's users.Deleteremoves the provider from the server.
A finalizer (nauthera.) holds the resource until that is done. A
removal the server does not take keeps the resource in phase Deleting with reason
ProviderNotRemoved, and is retried.
Spec
spec.allowedEmailDomains
Type: []string | Optional
The email domains whose accounts may sign in or sign up through the provider, such as example.com, matched exactly: a subdomain is its own entry. Left empty, every domain is admitted. With any listed, an account whose ID token carries no verified address in one of them is refused, an account already linked included.
spec.clientId
Type: string | Required
The client_id the server is registered under at the provider.
spec.clientSecretRef
Type: object | Required
The Secret in this namespace holding the client secret the provider issued. Editing the Secret sends the new secret to the server.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | No | "client-secret" | The key within the Secret's data holding the client secret. |
name | string | Yes | — | The Secret's name. |
spec.deletionPolicy
Type: string | Optional
What deleting this resource does to the provider it registered. Retain leaves it registered, so deleting a namespace cannot take a sign-in method away from the organization's users. Delete removes it from the server. A removal that fails keeps the resource, in phase Deleting, until it succeeds.
Defaults to "Retain".
One of: Retain, Delete.
spec.displayName
Type: string | Optional
The name shown on the provider's sign-in button.
spec.mfaAcrValues
Type: []string | Optional
The acr values the provider issues for a multi-factor sign-in, for a provider that reports it in acr rather than amr. Needs trustMfa. A value holds no Unicode whitespace.
spec.organization
Type: string | Optional
The slug of the organization this provider belongs to. Needed only where more than one organization admits the namespace in its spec.namespaces; otherwise the one that does is taken. The main organization is "default". Immutable.
spec.providerIssuer
Type: string | Required
The provider's own issuer, whose discovery document the server reads (OpenID Connect Discovery 1.0 §4), such as https://accounts.google.com. It must be https with a public host; the server refuses one that is not. GitHub's, https://github.com/login/oauth, signs in through GitHub's OAuth 2.0 flow and REST API instead, since GitHub issues no ID token: its numeric user id is the subject and its primary address the email, and it cannot be trusted for MFA.
spec.scopes
Type: []string | Optional
The scopes requested from the provider. openid is always requested; left empty, the server asks for openid, email and profile. GitHub is asked for these alone, and for read:user and user:email when they are left empty.
spec.slug
Type: string | Optional
The provider's name in its callback URL and in an OIDCClient's spec.identityProviders: a lowercase letter followed by up to 62 lowercase letters, digits or dashes. Defaults to the resource's name. Immutable.
spec.spareSecondFactor
Type: boolean | Optional
Whether a sign-in through the provider skips the server's second factor, neither challenged nor sent to enroll one, when the provider did not vouch for one (trustMfa). Such a sign-in is single-factor: its session and tokens carry no amr mfa. For a provider whose users secure their own accounts but that reports nothing about how they signed in, such as Google or GitHub.
Defaults to false.
spec.trustEmail
Type: boolean | Optional
Whether the provider's email_verified claim proves that its user controls the address, which lets a sign-in through the provider link to an existing account holding that address. Leave it false for a provider that lets its users claim an address they do not control.
Defaults to false.
spec.trustMfa
Type: boolean | Optional
Whether the provider's own multi-factor sign-in stands in for the server's second factor: a sign-in whose ID token has an amr holding mfa, or an acr listed in mfaAcrValues, is not asked for a one-time password. Leave it false unless the provider enforces the MFA it reports.
Defaults to false.
Status
The operator writes status from the server's answer. status.redirectUri is the URI to
register at the provider, and status.secretFingerprint identifies the client secret the
server holds.
phase is one of Pending, Ready, Error or Deleting. Beside the connection
reasons every resource shares (Refused, Unreachable, Unsupported,
CredentialUnavailable, TrustAnchorUnavailable) and the namespace reasons
(NamespaceNotAdmitted, OrganizationAmbiguous, OrganizationNotReady,
OrganizationChanged), both explained in the
OIDCClient reference, the Ready condition's reason is
one of:
| Reason | Phase | Meaning |
|---|---|---|
Registered | Ready | The server holds the current generation and the current secret. The message repeats the redirect URI to register. |
NamespaceNotCovered | Error | The resource's namespace is not in the operator's --namespaces. Nothing is sent. |
SecretNotFound | Error | The Secret spec.clientSecretRef names does not exist in the resource's namespace. |
SecretKeyMissing | Error | The Secret holds nothing under the key. |
SecretPadded | Error | The value begins or ends with whitespace. |
ProviderNotRemoved | Deleting | Under deletionPolicy: Delete, the server has not removed the provider yet. |
When sign-in through a provider fails, its auth.sign_in.completed audit record has the
cause upstream_unavailable, with status and upstream_error saying whether the
provider could not be reached or refused the server. invalid_client is a client ID or
secret the provider does not accept: check spec.clientId and the Secret against the
client registered at the provider.
| Field | Type | Description |
|---|---|---|
conditions | []object | Standard Kubernetes conditions (Ready, ...). |
etag | string | The server's revision of the provider as the operator last wrote it. |
issuer | string | The issuer the server homed the provider at, after its default was applied. |
lastReconciledTime | string | When the operator last reconciled the resource, whatever the outcome. A stale value means the operator is not running, not the leader, or cannot see the resource. |
observedGeneration | integer | .metadata.generation last reconciled by the server. |
organization | string | The slug of the organization the server holds the provider in. Once set, a namespace that comes to configure another organization is refused rather than moving the provider. |
phase | string | High-level lifecycle phase. |
redirectUri | string | The redirect URI to register at the provider for this server: where the provider sends its users back after they sign in. |
secretFingerprint | string | A non-reversible fingerprint of the client secret the server holds, as the server reports it; never the secret. |
Related
- Social sign-in: what users see
- OIDCClient: opting a client in with
spec.identityProviders - Organizations: the tenant a provider belongs to
- Operator: installing the operator and its grant