# Existing identity providers

> How Nauthera fits next to an identity provider you already run, such as Entra ID, Okta or Google Workspace: federation, trust, account linking and leavers.

Most companies that adopt Nauthera already have an identity provider for their
workforce: Entra ID, Okta, Google Workspace, Auth0 or Keycloak. You don't have to replace
it. Nauthera can sit between your applications and that provider, as their OpenID
Connect issuer, while the existing provider stays where people's accounts, passwords and
company second factor live.

## Three ways to fit in

| Pattern | Users sign in with | Good for |
|---|---|---|
| **Federate** (broker) | The existing provider, through a button on Nauthera's sign-in screen | Workforce applications, when the company directory stays the source of truth |
| **Replace** | A password, passkey and second factor held by Nauthera | Applications whose users are not in any company directory, such as customers or partners, or a company retiring its provider |
| **Both** | Either, per client | A customer-facing app that also lets staff in through the company provider |

```mermaid
flowchart LR
  apps["Your applications"] -->|OpenID Connect| N["Nauthera<br/>their issuer"]
  N -->|OpenID Connect| U["Entra ID, Okta<br/>or Google"]
  U --- D[("Company<br/>directory")]
```

The pattern is chosen per [organization](https://www.nauthera.io/docs/v1/organizations) and per client, not
for the whole deployment. A common shape is one organization for the workforce,
federated to Entra ID, and one or more for customers, with their own accounts.

## What federating gives you

With Nauthera as the applications' issuer and your provider upstream:

- **Applications are declared next to their code.** Each one is an
  [`OIDCClient`](https://www.nauthera.io/docs/v1/oidc-client) manifest in its own repository, reviewed and
  delivered by the [operator](https://www.nauthera.io/docs/v1/operator), instead of an app registration
  someone clicks together in the provider's portal.
- **One upstream registration.** Nauthera is registered once at the provider. New
  applications don't need a new registration there, or a ticket to the team that runs it.
- **Tokens shaped for your APIs.** Resource indicators, DPoP-bound access tokens,
  `private_key_jwt` and per-client lifetimes, set in the manifest (see
  [Tokens & Sender Constraint](https://www.nauthera.io/docs/v1/tokens)).
- **The provider's second factor counts.** With `trustMfa`, a sign-in the provider
  already verified with MFA is not asked for a second factor again.
- **One audit trail** of who signed in to which application, in Nauthera's
  [audit log](https://www.nauthera.io/docs/v1/audit-log), beside the provider's own sign-in log.

What it does not give you:

- **No SAML.** The upstream provider must speak OpenID Connect, as Entra ID, Okta,
  Google, Auth0 and Keycloak all do, or be GitHub, which signs in with OAuth 2.0 and its
  REST API. Every application must speak OpenID Connect too. An LDAP directory or Active
  Directory connects through an [`LDAPDirectory`](https://www.nauthera.io/docs/v1/ldap-directory-resource)
  instead.
- **No group or role claims from upstream.** Nauthera reads the provider's ID token, or
  GitHub's user API, to find or create the account; it does not copy the provider's
  groups into Nauthera's tokens. Applications that authorize by group keep reading them
  from the provider's APIs, or you model them as scopes in Nauthera.
- **No SCIM.** Accounts are created in Nauthera on a user's first sign-in, not pushed
  from the directory. See [Leavers and deprovisioning](#leavers-and-deprovisioning).
- **A public provider.** The server fetches the provider's discovery document, token
  endpoint and keys itself, and refuses loopback and private addresses. A provider
  reachable only inside your network, such as an internal AD FS, cannot be used.

## Setting it up

1. **Register Nauthera at the provider** as a web application (a confidential client with
   the authorization code flow). Put the client secret in a Kubernetes Secret.
2. **Declare the provider** with an [`IdentityProvider`](https://www.nauthera.io/docs/v1/identity-provider-resource)
   resource in the organization, and choose the trust settings below.
3. **Register the redirect URI.** Once the resource is `Ready`, add its
   `status.redirectUri` (`<organization issuer>/v1/auth/federation/<slug>/callback`) at
   the provider.
4. **Offer it per client.** Each `OIDCClient` lists the provider under
   `spec.identityProviders`, with `allowSignIn` and, for just-in-time accounts,
   `allowSignUp`.

```mermaid
sequenceDiagram
  participant B as Browser
  participant A as Application
  participant N as Nauthera
  participant U as Entra ID
  B->>A: Open the app
  A-->>B: Redirect to Nauthera
  B->>N: Continue with Entra ID
  N-->>B: Redirect to Entra ID
  B->>U: Sign in, with MFA
  U-->>B: Redirect to Nauthera's callback
  B->>N: Code from Entra ID
  N->>U: Redeem it, read the ID token
  Note over N: Find, link or create the account
  N-->>B: Redirect with a code
  B->>A: Code from Nauthera
  A->>N: Redeem it for Nauthera's tokens
```

```yaml
apiVersion: nauthera.io/v1alpha1
kind: IdentityProvider
metadata:
  name: entra
  namespace: staff              # a namespace the staff organization admits
spec:
  slug: entra
  displayName: Example Corp account
  providerIssuer: https://login.microsoftonline.com/<tenant-id>/v2.0
  clientId: <application (client) id>
  clientSecretRef:
    name: entra-oidc
  trustEmail: true
  allowedEmailDomains: [example.com]
  trustMfa: true
```

Use a single-tenant issuer, with your tenant's ID, for Entra ID. The multi-tenant
`common` and `organizations` endpoints do not have one issuer, and would let accounts of
other tenants through.

## Choosing the trust settings

| Setting | Turn it on when | Leave it off when |
|---|---|---|
| `trustEmail` | The provider only issues addresses the company controls, and its `email_verified` is reliable. Needed to link to accounts that already exist, and for `allowedEmailDomains`. | The provider lets users claim any address, such as a consumer provider used for sign-up. |
| `allowedEmailDomains` | Only company addresses should get in through this provider. Recommended for every workforce provider. | The provider is for customers with any address. |
| `trustMfa` | The provider enforces MFA itself, through conditional access or a sign-on policy, and reports it in `amr` (Entra ID, Okta, Auth0) or in `acr` (set `mfaAcrValues`). | The provider does not enforce MFA, or does not report it. Google sends neither claim, so `trustMfa` has no effect for it; GitHub refuses it. |
| `spareSecondFactor` | The provider's users secure their own accounts, but it reports nothing about how they signed in (Google, GitHub), and its sign-ins should not meet Nauthera's second factor. They stay single-factor. | Any application of the organization needs a second factor for these users: the setting overrides the second-factor policy and its rules. |

`trustMfa` moves the second factor out of Nauthera. Turn it on only after checking, in the
provider's own policy, that every sign-in that reaches Nauthera passed MFA. A sign-in
whose token carries neither claim gets Nauthera's own second factor.

## Accounts, linking and sign-up

A sign-in through the provider ends in one Nauthera account:

- **Linked before:** the same account as last time, found by the provider's subject.
- **An account with the same address exists:** linked, but only with `trustEmail`, a
  verified address from the provider, and an address the account has verified too.
- **No account:** created, when the client sets `allowSignUp` and the deployment has
  `auth.registration.enabled`.

```mermaid
flowchart TD
  T["ID token from the provider"] --> L["Linked before?"]
  L -->|yes| S1["Sign in to that account"]
  L -->|no| E["Account with the<br/>same address?"]
  E -->|yes| V["trustEmail, and verified<br/>on both sides?"]
  V -->|yes| S2["Link it and sign in"]
  V -.->|no| R1["Refused"]
  E -->|no| G["allowSignUp and<br/>registration on?"]
  G -->|yes| S3["Create the account"]
  G -.->|no| R2["Refused"]
```

`auth.registration.enabled` is **server-wide**. Turning it on so that first sign-ins
through the provider create accounts also opens password sign-up at `/register` in every
organization. If you want accounts created only through the provider, keep registration
off and create the accounts ahead of time instead (an administrator creates them with
`email_verified: true`, and the first sign-in through a provider trusted for email links
to them), or accept open sign-up and restrict it with `allowedEmailDomains`, which only
applies to the provider.

Applications receive Nauthera's `sub`, not the provider's. It is the account's id in
Nauthera and stays the same however the user signs in.

## Leavers and deprovisioning

Without SCIM, disabling someone in the company directory does not reach Nauthera by
itself. It stops their next sign-in through the provider, but:

- their Nauthera sign-in session keeps working until it ends
  (`auth.session.idle_ttl` 12h, `auth.session.absolute_ttl` 24h by default),
- their refresh tokens keep working, since Nauthera does not ask the provider again on a
  refresh (`issuer.refresh_token_ttl`, 720h, renewed on every rotation),
- access tokens already issued stay valid until they expire (`issuer.access_token_ttl`,
  10m),
- a password set through the forgot-password link still signs in, where the
  organization has an email relay, since the password form stays on the sign-in screen.

```mermaid
flowchart LR
  HR["Leaver"] --> DIR["Disabled in<br/>the directory"]
  DIR -->|stops| NEW["New sign-ins<br/>through the provider"]
  HR --> BLK["Blocked in<br/>Nauthera"]
  BLK -->|ends| ALL["Sessions and<br/>refresh tokens"]
```

So a leaver process needs a step in Nauthera: block the account through the
[admin API](https://www.nauthera.io/docs/v1/user-management#account-status). A blocked user can neither sign
in nor obtain or renew a token. Blocking ends every session they hold, retires their
refresh tokens, tells each application that took part in a session over back-channel
logout, and keeps the account and its history for investigation. Signing them out
everywhere (`DELETE /v1/admin/orgs/{slug}/users/{id}/sessions`) ends their sessions and
retires their refresh tokens, and keeps the account; deleting the user does the same
and then removes the account. Start it from the event your HR or directory system
already emits:

```bash
curl -s -X POST \
  "https://auth.example.com/v1/admin/orgs/staff/users/$USER_ID/block?reason=Leaver+HR-1234" \
  -H "Authorization: DPoP $TOKEN" -H "DPoP: $PROOF"
```

Each of these needs an administrator's recent sign-in, so a person runs it rather than
the `{slug}-admin-m2m` client: have the event open a task for an administrator, and keep
refresh lifetimes short for the workforce organization's clients
(`refreshTokenTtlSeconds`) so access lapses on its own soon after the upstream account is
disabled.

## Replacing the provider instead

If the company is retiring its provider, or the users were never in one, Nauthera holds
the accounts itself: passwords hashed with argon2id, TOTP and passkeys, recovery codes,
invitations and password reset by email. Moving accounts and applications over is covered
in [Migration](https://www.nauthera.io/docs/v1/adoption/migration). Federating first, then removing the
provider once every account has a Nauthera password or passkey, is a way to move without
a forced password reset on day one.
