> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alginte.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OIDC provider setup

> Connect Alginte's OIDC login to Keycloak, Google, Microsoft Entra ID, or Okta — any OIDC-compliant provider works.

Alginte's single sign-on uses standard **OpenID Connect** (authorization-code flow).
Any OIDC-compliant provider works; the walkthroughs below cover four common ones.

## What Alginte needs from any provider

Register Alginte at your provider as a **confidential web application** (not a
public/SPA client — Alginte's backend performs the code-for-token exchange
server-side). That registration yields three artifacts:

1. **Client id** and **client secret**.
2. **Issuer URI** — the provider's realm/tenant base URL. Alginte discovers every
   endpoint from `<issuer>/.well-known/openid-configuration` at startup — which is
   also why the backend refuses to start if the provider is unreachable.
3. A registered **redirect URI**: `http(s)://<alginte-host>/login/oauth2/code/<registration-id>`.
   The `<registration-id>` is a name you choose (e.g. `keycloak`, `google`, `entra`,
   `okta`) — it appears in the property keys below, on the login button
   ("Sign in with \<registration-id>"), and in the callback path.

Request the scopes `openid`, `profile`, `email` — the `email` claim powers the
allowlist's domain matching.

## The property block

```properties theme={null}
spring.security.oauth2.client.registration.<registration-id>.client-id=...
spring.security.oauth2.client.registration.<registration-id>.client-secret=...
spring.security.oauth2.client.registration.<registration-id>.scope=openid,profile,email
spring.security.oauth2.client.provider.<registration-id>.issuer-uri=https://...

# MANDATORY with OIDC — the backend refuses to start without one:
alginte.auth.allowed-users=alice,bob@example.com
# and/or
alginte.auth.allowed-domains=example.com
```

Every property can also be an environment variable (uppercase, dots → underscores,
**dashes removed** — see [Configuration](/operate/configuration)). For a Docker
deployment that keeps the secret out of any file:

```shell theme={null}
docker run -p 8888:8888 \
  -e SPRING_KAFKA_BOOTSTRAPSERVERS=<kafka-host>:9092 \
  -e SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENTID=alginte \
  -e SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENTSECRET=<secret> \
  -e SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_SCOPE=openid,profile,email \
  -e SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KEYCLOAK_ISSUERURI=https://<keycloak-host>/realms/<realm> \
  -e ALGINTE_AUTH_ALLOWEDDOMAINS=example.com \
  alginte/alginte:latest
```

<Warning>
  Keep the client secret out of version control — supply it via an environment
  variable or a file excluded from your repo.
</Warning>

## The allowlist is not optional

A bare OIDC login admits **anyone the provider authenticates** — every Gmail
account, every user in the tenant. Alginte therefore **fails startup** if OIDC is
configured without `alginte.auth.allowed-users` / `allowed-domains`.

* `allowed-users` entries match the provider-asserted **username or email**.
* `allowed-domains` entries match the **email's domain**.
* Matching is case-insensitive; empty lists admit nobody.
* A user who authenticates at the provider but isn't allowlisted is bounced back to
  the login screen with an explicit message and no session.

<Note>
  With multiple providers, the allowlist is **shared across all of them** — one list
  for the whole instance, not per provider.
</Note>

## Provider walkthroughs

<Tabs>
  <Tab title="Keycloak">
    1. In your realm: **Clients → Create client** — OpenID Connect, **client
       authentication on** (confidential), standard flow only.
    2. Register the redirect URI `https://<alginte-host>/login/oauth2/code/keycloak`.
    3. Copy the client secret from the **Credentials** tab.
    4. Issuer: `https://<keycloak-host>/realms/<realm>`.
    5. Add `spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username`
       so the allowlist can match Keycloak usernames (the default claim is the opaque `sub`).
  </Tab>

  <Tab title="Google">
    1. [Google Cloud Console → APIs & Services → Credentials](https://console.cloud.google.com/apis/credentials)
       → **Create credentials → OAuth client ID** → application type **Web
       application**; configure the OAuth consent screen if prompted.
    2. Register the redirect URI `https://<alginte-host>/login/oauth2/code/google`.
    3. Use the registration id `google` and Spring Boot supplies the provider
       metadata itself — **no `issuer-uri` needed**; client-id + client-secret + the
       allowlist suffice.
    4. Google accounts have opaque usernames — allowlist by **email**
       (`allowed-users=alice@gmail.com`) or, for a Workspace org, by **domain**
       (`allowed-domains=example.com`).
  </Tab>

  <Tab title="Microsoft Entra ID">
    1. [Azure portal](https://portal.azure.com) → **Microsoft Entra ID → App
       registrations → New registration**; platform **Web**, redirect URI
       `https://<alginte-host>/login/oauth2/code/entra`.
    2. Create a client secret under **Certificates & secrets**.
    3. Issuer: `https://login.microsoftonline.com/<tenant-id>/v2.0` (single-tenant is
       the sane default — but the allowlist stays mandatory regardless: tenant
       membership alone is not Alginte access).
    4. The `preferred_username` claim carries the UPN (usually the email) —
       `user-name-attribute=preferred_username` makes `allowed-users` entries match
       it directly.
  </Tab>

  <Tab title="Okta">
    1. Okta Admin Console → **Applications → Create App Integration** →
       **OIDC — Web Application**.
    2. Register the redirect URI `https://<alginte-host>/login/oauth2/code/okta`.
    3. Issuer: `https://<your-okta-domain>` (or a custom authorization server, e.g.
       `https://<your-okta-domain>/oauth2/default`).
    4. Okta usernames are emails — both allowlist forms work naturally.
  </Tab>
</Tabs>

## Behind a reverse proxy (HTTPS)

Spring computes the redirect URI from the incoming request, so behind a
TLS-terminating proxy the **public** URL must be what Spring sees:

1. Forward the standard `X-Forwarded-*` headers from the proxy.
2. Set `server.forward-headers-strategy=framework` (env: `SERVER_FORWARDHEADERSSTRATEGY=framework`).
3. Register the public `https://...` redirect URI at the provider.

Symptom of getting this wrong: the provider rejects the login with a
**redirect-URI-mismatch** error, because Spring built an internal `http://` URL.

## Multiple providers

Registrations are additive: configure several
`spring.security.oauth2.client.registration.<id>.*` blocks and the login screen
renders one "Sign in with \<id>" button per registration. The single-account
[username/password login](/operate/authentication) composes with all of them.

<Warning>
  The allowlist is shared, and a list property is **replaced, not merged**, by
  whichever configuration source wins — so the winning source must carry the *full*
  list for all providers (e.g. `allowed-users=alice,you@gmail.com`), not just the
  newest provider's entry.
</Warning>

## Troubleshooting

| Symptom                                                   | Cause                                                                                                                                                                                                         |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Backend refuses to start; message names the allowlist     | OIDC registration present but `alginte.auth.allowed-users` / `allowed-domains` empty — the deliberate fail-fast guard.                                                                                        |
| Backend refuses to start; connection error during startup | Issuer discovery failed — the provider is unreachable or the `issuer-uri` is wrong.                                                                                                                           |
| Provider shows a redirect-URI-mismatch error              | The URI registered at the provider doesn't match what Spring computed — check host, scheme (see the reverse-proxy section), and the `<registration-id>` in the path.                                          |
| Login screen shows "not on the Alginte allowlist"         | Authentication succeeded, the allowlist rejected — check which claim you're matching: `allowed-users` compares the username (the `user-name-attribute` claim) *or* email; `allowed-domains` the email domain. |
| One provider works, a second is always rejected           | The shared allowlist was replaced by the newest configuration source — carry the full list in whichever source wins (see *Multiple providers*).                                                               |
| Sign-out, then re-login without a password prompt         | Expected: logout ends the app session, not the provider's SSO session.                                                                                                                                        |
