# IdentityProvider

> Reference for the nauthera.io/v1alpha1 IdentityProvider resource, which lets an organization's users sign in with an external provider such as Google or GitHub.

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](https://www.nauthera.io/docs/v1/sign-in-flows/social-sign-in). The [operator](https://www.nauthera.io/docs/v1/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/crds/identityprovider-crd.yaml` in nauthera-server |

A provider is offered to no client by default. Each [OIDCClient](https://www.nauthera.io/docs/v1/oidc-client)
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:

```bash
kubectl -n nauthera create secret generic google-oidc \
  --from-literal=client-secret=<the secret Google issued>
```

```yaml
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: Retain
```

Once the resource is `Ready`, register `status.redirectUri` as an authorized redirect URI
of the client at the provider. It is
`<organization issuer>/v1/auth/federation/<slug>/callback`.

### GitHub

GitHub issues no ID token, so it is not an OpenID Connect provider. With
`providerIssuer: https://github.com/login/oauth` 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`.

```yaml
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: true
```

A 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:

```yaml
apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
  name: example-webapp
  namespace: nauthera
spec:
  clientId: example-webapp
  # …
  identityProviders:
    - slug: google
      allowSignIn: true
      allowSignUp: true
```

`kubectl 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.grpc.tls.allowed_clients` entry. The Helm chart's default
entry (`certManager.grpcTLS.allowedClients`) 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](https://www.nauthera.io/docs/v1/cli)).

## Trust settings

- **`trustEmail`** says whether the provider's `email_verified` claim 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 it `false` for a provider that lets its
  users claim an address they do not control.
- **`allowedEmailDomains`** admits 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 needs `trustEmail: true`, since only a trusted provider's verified address
  proves a domain; without it the server refuses the resource.
- **`trustMfa`** lets the provider's own multi-factor sign-in stand in for the server's
  second factor. A sign-in whose ID token has an `amr` holding `mfa` (RFC 8176) is not
  asked for, or made to set up, a second factor, and its session carries `amr` `mfa` and
  `acr` `aal2`. For a provider that reports MFA in `acr` instead, list those values in
  `mfaAcrValues`, which needs `trustMfa: true`. A token with neither gets the server's own
  second factor. The `auth.sign_in.completed` audit record names the claim that vouched
  in `upstream_mfa`. Turn it on only for a provider that enforces the MFA it reports:
  Entra ID, Okta and Auth0 send `amr`; Google sends neither claim, and GitHub cannot be
  trusted for it.
- **`spareSecondFactor`** lets 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 carry `amr` `fed` and no `mfa`, and its
  `auth.sign_in.completed` record says `mfa_spared: identity_provider`. A sign-in the
  provider does vouch for under `trustMfa` still gets `mfa` and `aal2`, 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.registration.enabled`; see [Sign up](https://www.nauthera.io/docs/v1/sign-in-flows/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
  `Error` under `SecretNotFound` or `SecretKeyMissing`, naming it. A value that begins or
  ends with whitespace, such as the newline `echo` leaves, is `Error` under
  `SecretPadded`: 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](https://www.nauthera.io/docs/v1/operator#which-organization-a-resource-configures));
  `spec.organization` names it where several organizations admit the namespace, and once
  set cannot be changed.

### Deleting a resource

`spec.deletionPolicy` decides, and defaults to `Retain`:

- **`Retain`** leaves the provider registered, so deleting a namespace cannot take a
  sign-in method away from the organization's users.
- **`Delete`** removes the provider from the server.

A finalizer (`nauthera.io/identityprovider`) 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

{/* AUTO-GENERATED-SPEC-START */}

### `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`.

{/* AUTO-GENERATED-SPEC-END */}

## 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](https://www.nauthera.io/docs/v1/oidc-client#status), 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.

{/* AUTO-GENERATED-STATUS-START */}

| 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. |

{/* AUTO-GENERATED-STATUS-END */}

## Related

- [Social sign-in](https://www.nauthera.io/docs/v1/sign-in-flows/social-sign-in): what users see
- [OIDCClient](https://www.nauthera.io/docs/v1/oidc-client): opting a client in with `spec.identityProviders`
- [Organizations](https://www.nauthera.io/docs/v1/organizations): the tenant a provider belongs to
- [Operator](https://www.nauthera.io/docs/v1/operator): installing the operator and its grant
