# OIDC / Single Sign-On SnapOtter supports OpenID Connect (OIDC) for single sign-on. Users can log in with an external identity provider such as Keycloak, Authentik, or Google instead of (or alongside) local username/password authentication. ## Quick start Add these environment variables to your `docker-compose.yml`: ```yaml services: SnapOtter: image: snapotter/snapotter:latest environment: EXTERNAL_URL: "https://photos.example.com" OIDC_ENABLED: "true" OIDC_ISSUER_URL: "https://auth.example.com/realms/myrealm" OIDC_CLIENT_ID: "snapotter" OIDC_CLIENT_SECRET: "your-secret-here" ``` The redirect URI for your provider is always: ``` ${EXTERNAL_URL}/api/auth/oidc/callback ``` For example, if `EXTERNAL_URL` is `https://photos.example.com`, configure your provider's redirect URI as `https://photos.example.com/api/auth/oidc/callback`. ## Configuration reference | Variable | Default | Description | |---|---|---| | `OIDC_ENABLED` | `false` | Enable OIDC login. A "Sign in with SSO" button appears on the login page. | | `OIDC_ISSUER_URL` | | Provider's issuer URL. Must support OIDC Discovery (`/.well-known/openid-configuration`). | | `OIDC_CLIENT_ID` | | OAuth client ID registered with your provider. | | `OIDC_CLIENT_SECRET` | | OAuth client secret. | | `OIDC_SCOPES` | `openid profile email` | Space-separated list of scopes to request. | | `OIDC_AUTO_CREATE_USERS` | `true` | Automatically create a local user account on first OIDC login. | | `OIDC_DEFAULT_ROLE` | `user` | Role assigned to auto-created OIDC users. One of `admin`, `editor`, or `user`. | | `OIDC_AUTO_LINK_USERS` | `false` | Link an OIDC identity to an existing local user if the email address matches. | | `OIDC_PROVIDER_NAME` | | Display name shown on the login button (e.g. "Keycloak", "Google"). If empty, the button says "SSO". | | `OIDC_CLOCK_TOLERANCE` | `30` | Clock skew tolerance in seconds for token validation. | | `OIDC_USERNAME_CLAIM` | `preferred_username` | ID token claim used as the username for new accounts. | | `EXTERNAL_URL` | | The public URL where SnapOtter is reachable. Required for OIDC to build the correct redirect URI. | | `COOKIE_SECRET` | auto-generated | Secret for signing session cookies. Set this explicitly when running multiple replicas. | ## Provider guides ### Keycloak 1. Create a new realm (or use an existing one). 2. Go to **Clients** and create a new client: - **Client ID**: `snapotter` - **Client authentication**: On (confidential) - **Authentication flow**: Standard flow (Authorization Code) 3. Under the client's **Settings** tab, set **Valid redirect URIs** to your callback URL (e.g. `https://photos.example.com/api/auth/oidc/callback`). 4. Copy the **Client secret** from the **Credentials** tab. 5. Set `OIDC_ISSUER_URL` to `https://keycloak.example.com/realms/your-realm`. ### Authentik 1. In the admin interface, go to **Applications > Providers** and create a new **OAuth2/OpenID Provider**. - **Client type**: Confidential - **Redirect URIs**: Your callback URL - **Signing key**: Select an existing key or create one 2. Create an **Application** and link it to the provider. 3. Copy the **Client ID** and **Client Secret** from the provider settings. 4. Set `OIDC_ISSUER_URL` to `https://authentik.example.com/application/o/snapotter/` (the trailing slash matters). ### Google 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). 2. Create a project (or select an existing one). 3. Navigate to **APIs & Services > OAuth consent screen** and configure it. 4. Go to **APIs & Services > Credentials** and create an **OAuth 2.0 Client ID**: - **Application type**: Web application - **Authorized redirect URIs**: Your callback URL 5. Copy the **Client ID** and **Client secret**. 6. Set `OIDC_ISSUER_URL` to `https://accounts.google.com`. 7. Set `OIDC_USERNAME_CLAIM` to `email` (Google does not provide `preferred_username`). ## User provisioning ### Auto-create When `OIDC_AUTO_CREATE_USERS` is `true` (the default), a local user account is created the first time someone logs in via OIDC. The username is taken from the claim specified by `OIDC_USERNAME_CLAIM`, and the role is set to `OIDC_DEFAULT_ROLE`. If a username collision occurs, a numeric suffix is appended (e.g. `jane` becomes `jane_2`). ### Auto-link When `OIDC_AUTO_LINK_USERS` is `true`, SnapOtter links an OIDC identity to an existing local account if the email addresses match. This is useful when you have pre-created user accounts and want them to start using SSO without losing their data. ::: warning Only enable auto-link if you trust your OIDC provider to verify email addresses. An unverified email could allow someone to take over another user's account. ::: ### Disabling local login OIDC does not disable local username/password login. Both methods remain available. Admins can still log in with local credentials if the OIDC provider is unreachable. ## Self-signed certificates If your OIDC provider uses a self-signed or private CA certificate, mount the CA bundle into the container and point `NODE_EXTRA_CA_CERTS` to it: ```yaml services: SnapOtter: image: snapotter/snapotter:latest volumes: - ./my-ca.pem:/etc/ssl/certs/custom-ca.pem:ro environment: NODE_EXTRA_CA_CERTS: /etc/ssl/certs/custom-ca.pem OIDC_ENABLED: "true" OIDC_ISSUER_URL: "https://auth.internal.example.com/realms/myrealm" OIDC_CLIENT_ID: "snapotter" OIDC_CLIENT_SECRET: "your-secret-here" ``` ::: danger Do not set `NODE_TLS_REJECT_UNAUTHORIZED=0`. This disables all TLS verification and is a security risk. ::: ## Troubleshooting ### Redirect URI mismatch The most common error. Check for these differences between what your provider expects and what SnapOtter sends: - `http` vs `https` - the scheme must match exactly - Trailing slash - some providers are strict about this - Port number - include the port if it is non-standard - Path - must be `/api/auth/oidc/callback` Double-check `EXTERNAL_URL`. It must match the URL users type in their browser. ### UNABLE_TO_VERIFY_LEAF_SIGNATURE The OIDC provider is using a certificate that Node.js does not trust. See [Self-signed certificates](#self-signed-certificates) above. ### Clock skew errors If your server clock and the OIDC provider clock are out of sync, token validation may fail. Increase `OIDC_CLOCK_TOLERANCE` (default is 30 seconds). A better fix is to run NTP on both machines. ### "OIDC provider unreachable" SnapOtter fetches the provider's discovery document at startup and during login. Check: - DNS resolution from inside the Docker container (`docker exec snapotter nslookup auth.example.com`) - Firewall rules between the container and the provider - The `OIDC_ISSUER_URL` value - it must be reachable from the server, not just from your browser ### Missing claims If usernames or emails are empty after login, your provider may not be returning the expected claims. Verify: - The scopes configured in `OIDC_SCOPES` include `profile` and `email` - The provider is configured to include the claim specified in `OIDC_USERNAME_CLAIM` in the ID token - Some providers require explicit mapper/scope configuration to release claims