Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/administration/oidc/authelia.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ environment:

## 5. Set your email

In RomM's **Profile**, set your email to exactly the same address Authelia has for you. RomM matches OIDC users to existing accounts by email.
In RomM's **Profile**, set your email to exactly the same address Authelia has for you. RomM matches an existing account by email on its first OIDC login, then by the Authelia user it linked, so a later email change in Authelia carries over.

![Set email](../../resources/authelia/1-user-profile.png)

Expand Down
17 changes: 13 additions & 4 deletions docs/administration/oidc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ OpenID Connect (OIDC) lets users sign in through an external identity provider:
2. They're redirected to your provider.
3. They authenticate (password, passkey, MFA, whatever your provider enforces).
4. Provider redirects back to `{ROMM_BASE_URL}/api/oauth/openid` with an authorisation code.
5. The code is exchanged for an ID token, the user's email and role claims are read, and either a matching local user is created on the fly (unless you've [turned off registration](#auto-provisioning)), or an existing one is logged in.
5. The code is exchanged for an ID token and the user's email, username and role claims are read. Claims the ID token leaves out are fetched from the provider's UserInfo endpoint.
6. The matching local user is logged in (see [Account matching](#account-matching)), or a new one is created on the fly unless you've [turned off registration](#auto-provisioning).

## Provider guides

Expand Down Expand Up @@ -70,7 +71,7 @@ environment:
- OIDC_ROLE_ADMIN=romm-admin,platform-admins # group values → Admin
```

On every login, the claim named by `OIDC_CLAIM_ROLES` is read (often `groups`, or `realm_access.roles` on Keycloak, so check your provider's token output). If a value matches `OIDC_ROLE_ADMIN`, the user becomes an Admin.
On every login, the claim named by `OIDC_CLAIM_ROLES` is read (often `groups`, or `realm_access.roles` on Keycloak, so check your provider's ID token or UserInfo response). If a value matches `OIDC_ROLE_ADMIN`, the user becomes an Admin.

Roles are re-evaluated on every login, so demoting someone on the IdP side takes effect the next time they sign in.

Expand Down Expand Up @@ -98,6 +99,14 @@ Roles are re-evaluated on every login, so demoting someone on the IdP side takes

<!-- markdownlint-enable MD046 -->

## Account matching

The first OIDC login for an existing local account matches it by email, so set the account's email to exactly the address your provider has for the user. That login links the account to the user's identity at the provider (the token's `iss` issuer and `sub` subject). Every later login matches on that identity:

- **Email changes at the provider carry over.** The user still signs into the same account, and RomM stores the new email, unless another account already uses it.
- **Switching providers keeps accounts.** A login from a new issuer matches by email again and relinks the account to the new provider.
- **A new subject for a linked email is refused.** When the same provider sends a different subject for an email that is already linked, RomM rejects the login with a 403 rather than hand the account to someone else. This happens when the provider reassigns the email or recreates the user. See [Authentication Troubleshooting](../../troubleshooting/authentication.md#this-account-is-linked-to-a-different-identity-at-the-provider) to relink it.

## Autologin

To bypass the login page entirely and redirect straight to the IdP, so RomM feels like a native part of your SSO stack:
Expand Down Expand Up @@ -138,7 +147,7 @@ Whatever that attribute holds gets sanitised before it becomes a username to pre

## Important notes

- **Email must match** between OIDC and any existing local account, otherwise OIDC creates a new account alongside the old one.
- **Email must match** between OIDC and an existing local account on its first OIDC login, otherwise OIDC creates a new account alongside the old one. After that, the account follows the user's identity at the provider (see [Account matching](#account-matching)).
- **HTTPS is required** in production, as OIDC will refuse to redirect to a plain-HTTP `ROMM_BASE_URL`.
- Large drift between the RomM host and IdP will lead to **clock skew** and cause ID-token validation to fail.

Expand All @@ -147,4 +156,4 @@ Whatever that attribute holds gets sanitised before it becomes a username to pre
Common failures and fixes live in [Authentication Troubleshooting](../../troubleshooting/authentication.md). Two of the usual suspects:

- `redirect_uri_mismatch`: `OIDC_REDIRECT_URI` differs from what's registered at the provider. A trailing slash alone is enough to trigger it.
- User created but not made Admin: check `OIDC_CLAIM_ROLES` points at a claim that actually exists in the token, and that the group values match `OIDC_ROLE_ADMIN` exactly (case-sensitive).
- User created but not made Admin: check `OIDC_CLAIM_ROLES` points at a claim that actually exists in the ID token or the UserInfo response, and that the group values match `OIDC_ROLE_ADMIN` exactly (case-sensitive).
2 changes: 1 addition & 1 deletion docs/administration/oidc/zitadel.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Click **Create**. Zitadel shows the client secret only once, so copy it now.

## 4. Enable claims in the ID Token

Without this, RomM throws "Email is missing from token" on login. On the application's **Token Settings** tab, tick **User Info inside ID Token** and **Save**.
RomM fetches claims the ID token leaves out from Zitadel's UserInfo endpoint, and putting them in the ID token avoids that extra request. It also rules out "Email is missing from token" when the UserInfo response doesn't carry the email. On the application's **Token Settings** tab, tick **User Info inside ID Token** and **Save**.

## 5. Configure

Expand Down
16 changes: 13 additions & 3 deletions docs/troubleshooting/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ The `OIDC_REDIRECT_URI` in the env doesn't **exactly** match what's registered a

You configured `OIDC_CLAIM_ROLES` but it's not being honoured.

1. **Is the claim actually in the token?** Decode your IdP's ID token at [jwt.io](https://jwt.io) and verify the claim name (e.g. `groups`, `realm_access.roles`) is present and non-empty.
1. **Is the claim actually sent?** Decode your IdP's ID token at [jwt.io](https://jwt.io), or check its UserInfo response, and verify the claim name (e.g. `groups`, `realm_access.roles`) is present and non-empty. RomM reads the ID token first and falls back to UserInfo for a claim it leaves out.
2. **Does the value match?** `OIDC_ROLE_ADMIN=romm-admin` will only match if the claim contains exactly the string `romm-admin`, and it's case-sensitive.
3. **Is the claim mapper on the IdP side configured to include the claim?** On Keycloak, for example, you need a Client Scope with a Group Membership mapper added to the client.

Expand All @@ -96,9 +96,9 @@ environment:

`OIDC_ROLE_VIEWER` and `OIDC_ROLE_EDITOR` both resolve to **User**, but they still grant access when role claims are enabled (see [Role mapping](../administration/oidc/index.md#role-mapping)).

### "Email is missing from token" (Zitadel-specific)
### "Email is missing from token"

On Zitadel, open the application → **Token Settings** → tick **User Info inside ID Token** → Save (see [OIDC with Zitadel → Enable claims](../administration/oidc/zitadel.md) for the full walkthrough).
Neither the ID token nor the provider's UserInfo endpoint returned an `email` claim. Check that the client requests the `email` scope and that the provider releases it. On Zitadel, also open the application → **Token Settings** → tick **User Info inside ID Token** → Save (see [OIDC with Zitadel → Enable claims](../administration/oidc/zitadel.md) for the full walkthrough).

### Authentik 2025.10: login succeeds but the user is rejected

Expand All @@ -113,6 +113,16 @@ Two possibilities:
1. **Email not verified in Keycloak**: Admin Console → Users → open the user → **Email Verified**: on. Unverified emails are rejected.
2. **Email mismatch between Keycloak and a pre-existing local user**: if a local account `alice@example.com` already exists, the first OIDC login for `alice@example.com` signs into that account. If the emails don't match exactly, a _second_ account is created. Fix: edit the local user to set the correct email, then log in via OIDC.

### `This account is linked to a different identity at the provider`

The login's email belongs to a RomM account that is already linked to another user (subject) at the same provider. RomM refuses it so that an email reassigned at the provider can't take over the account. It also happens when you delete and recreate the user at the provider.

If the new provider user really is the account's owner, clear the stored link in the database, then log in again to relink it:

```sql
UPDATE users SET oidc_issuer = NULL, oidc_sub = NULL WHERE username = 'alice';
```

### `OAuthException: expired token` on callback

Your host and the IdP have significant clock drift, so run NTP on both.
Expand Down