link are checking that email matches, +email alias are normalize into email if email is already used on another account, link is not possible not usurpation risk as the IDP is choosen by the admin
6.4 KiB
OIDC / SSO
OxiCloud supports OpenID Connect for single sign-on with providers like Keycloak, Authentik, Authelia, Google, and Azure AD.
How It Works
- User clicks "Sign in with SSO" on the login page
- Browser redirects to the identity provider (IdP)
- User authenticates with their existing credentials
- IdP redirects back to OxiCloud with an auth code
- OxiCloud exchanges the code for user info and issues its own JWT tokens
Architecture
OIDC follows the Authorization Code Flow and keeps a clear split between provider communication and local session handling.
OidcServicediscovers provider metadata, builds authorization URLs, exchanges authorization codes, and validates the token responseAuthApplicationServicecoordinates user lookup or auto-provisioning and then issues OxiCloud's own access and refresh tokens- the auth handler exposes the public OIDC endpoints under
/api/auth/oidc/*
After the browser returns from the IdP, OxiCloud does not reuse the provider token for app requests. It converts the identity into its own JWT session model.
Configuration
OXICLOUD_OIDC_ENABLED=true
OXICLOUD_OIDC_ISSUER_URL="https://authentik.example.com/application/o/oxicloud/"
OXICLOUD_OIDC_CLIENT_ID="your-client-id"
OXICLOUD_OIDC_CLIENT_SECRET="your-client-secret"
OXICLOUD_OIDC_REDIRECT_URI="https://oxicloud.example.com/api/auth/oidc/callback"
OXICLOUD_OIDC_SCOPES="openid profile email"
OXICLOUD_OIDC_FRONTEND_URL="https://oxicloud.example.com"
OXICLOUD_OIDC_AUTO_PROVISION=true
OXICLOUD_OIDC_ADMIN_GROUPS="oxicloud-admins"
OXICLOUD_OIDC_PROVIDER_NAME="Authentik"
Variable Reference
| Variable | Default | Description |
|---|---|---|
OXICLOUD_OIDC_ENABLED |
false |
Master switch |
OXICLOUD_OIDC_ISSUER_URL |
— | Provider's OIDC issuer URL |
OXICLOUD_OIDC_CLIENT_ID |
— | OAuth client ID |
OXICLOUD_OIDC_CLIENT_SECRET |
— | OAuth client secret |
OXICLOUD_OIDC_REDIRECT_URI |
http://localhost:8086/api/auth/oidc/callback |
Callback URL registered with the IdP |
OXICLOUD_OIDC_SCOPES |
openid profile email |
Requested scopes |
OXICLOUD_OIDC_FRONTEND_URL |
http://localhost:8086 |
Where to redirect the browser after auth |
OXICLOUD_OIDC_AUTO_PROVISION |
true |
Auto-create users on first login |
OXICLOUD_OIDC_AUTO_LINK_EMAIL_MATCH |
true |
Auto-link existing local users to their OIDC identity when the IdP-returned email (with email_verified=true) matches an existing local account. See narrative below. |
OXICLOUD_OIDC_ADMIN_GROUPS |
— | OIDC groups that grant admin role |
OXICLOUD_OIDC_DISABLE_PASSWORD_LOGIN |
false |
DEPRECATED — use OXICLOUD_AUTH_METHODS=oidc instead. Emits a boot warning; slated for removal in next major release. |
OXICLOUD_OIDC_PROVIDER_NAME |
SSO |
Label shown on the login button |
::: warning
If OXICLOUD_OIDC_ENABLED=true but issuer_url, client_id, or client_secret are empty, OIDC is automatically disabled with an error log.
:::
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/auth/oidc/providers |
Returns OIDC provider info |
| GET | /api/auth/oidc/authorize |
Authorization URL for redirect to IdP |
| GET | /api/auth/oidc/callback |
Callback from IdP with auth code |
| POST | /api/auth/oidc/exchange |
Exchange auth code for JWT tokens |
Identity Mapping
OIDC users are matched by the pair:
federation_issuer(the id_tokenissclaim, canonical issuer URL)federation_subject(the id_tokensubclaim)
This allows one external identity to map to one local user record and supports just-in-time provisioning when OXICLOUD_OIDC_AUTO_PROVISION=true.
Auto-linking existing local users
When OXICLOUD_OIDC_AUTO_LINK_EMAIL_MATCH=true (default) and the subject-based lookup misses on an OIDC login, OxiCloud tries to match by the IdP-returned email address instead. If exactly one local user's email matches (under +alias-stripping normalization) AND the IdP returned email_verified=true, the OIDC identity is auto-linked to that existing user — no admin round-trip, no manual SQL, no self-service flow needed. Great UX for enabling SSO on top of an existing user base.
Refusal cases (fall through to the standard "email already exists" error):
email_verified=falseon the IdP claims — audit eventfederation.auto_link_refusedwithreason=auto_link_email_not_verified.- More than one local user's email normalizes to the same value (rare but possible with
alice@example.comandalice+work@example.com) — refused asemail_ambiguous. - The matched user is already linked to a different OIDC identity — refused as
already_linked_elsewhere.
Security model: safe under OxiCloud's single-IdP configuration (admin explicitly chose and configured the IdP; the email_verified gate means the IdP has vouched for the user's ownership of that email). NOT safe for future multi-IdP federation where any WebFinger-discovered IdP is accepted — deferred to that flow.
For users who need explicit consent for every OIDC link (compliance requirements), set OXICLOUD_OIDC_AUTO_LINK_EMAIL_MATCH=false. The self-service link flow (profile page "Connect Single Sign-On" button) remains available regardless.
Full decision tree and safety-check details in docs/plan/oidc-account-linking.md.
Provider Examples
Keycloak
# docker-compose.yml
services:
oxicloud:
environment:
OXICLOUD_OIDC_ENABLED: "true"
OXICLOUD_OIDC_ISSUER_URL: "https://keycloak.example.com/realms/your-realm"
OXICLOUD_OIDC_CLIENT_ID: "oxicloud"
OXICLOUD_OIDC_CLIENT_SECRET: "your-client-secret"
OXICLOUD_OIDC_REDIRECT_URI: "https://oxicloud.example.com/api/auth/oidc/callback"
OXICLOUD_OIDC_FRONTEND_URL: "https://oxicloud.example.com"
OXICLOUD_OIDC_PROVIDER_NAME: "Keycloak"
Authentik
OXICLOUD_OIDC_ISSUER_URL="https://authentik.example.com/application/o/oxicloud/"
OXICLOUD_OIDC_PROVIDER_NAME="Authentik"
OXICLOUD_OIDC_ISSUER_URL="https://accounts.google.com"
OXICLOUD_OIDC_PROVIDER_NAME="Google"
Notes
- Always use HTTPS for OIDC connections
- One OIDC provider per instance (single-provider model)
- OIDC users share the same permissions model as local users
- After OIDC auth, the backend issues its own JWT tokens (no IdP token dependency)
- Use the admin settings UI (
/admin.html) to configure and test OIDC at runtime