Skip to content

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, Google, or Microsoft Entra ID 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 ​

VariableDefaultDescription
OIDC_ENABLEDfalseEnable OIDC login. A "Sign in with SSO" button appears on the login page.
OIDC_ISSUER_URLProvider's issuer URL. Must support OIDC Discovery (/.well-known/openid-configuration).
OIDC_CLIENT_IDOAuth client ID registered with your provider.
OIDC_CLIENT_SECRETOAuth client secret.
OIDC_SCOPESopenid profile emailSpace-separated list of scopes to request.
OIDC_AUTO_CREATE_USERStrueAutomatically create a local user account on first OIDC login.
OIDC_DEFAULT_ROLEuserRole assigned to auto-created OIDC users. One of admin, editor, or user.
OIDC_AUTO_LINK_USERSfalseLink an OIDC identity to an existing local user if the email address matches.
OIDC_PROVIDER_NAMEDisplay name shown on the login button (e.g. "Keycloak", "Google"). If empty, the button says "SSO".
OIDC_CLOCK_TOLERANCE30Clock skew tolerance in seconds for token validation.
OIDC_USERNAME_CLAIMpreferred_usernameID token claim used as the username for new accounts.
EXTERNAL_URLThe public URL where SnapOtter is reachable. Required for OIDC to build the correct redirect URI.
COOKIE_SECRETauto-generatedSecret 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.
  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).

Azure AD / Entra ID ​

  1. In the Azure portal, go to Microsoft Entra ID > App registrations > New registration.
  2. Name the app "SnapOtter". Under Redirect URI, select the Web platform and enter your callback URL (e.g. https://photos.example.com/api/auth/oidc/callback). Do not pick Single-page application: SnapOtter authenticates with a client secret, which Entra ID rejects on SPA registrations.
  3. Copy the Application (client) ID and Directory (tenant) ID from the Overview page.
  4. Go to Certificates & secrets > New client secret and copy the secret Value right away. It is shown only once, and the adjacent Secret ID is not the secret.
  5. Set OIDC_ISSUER_URL to https://login.microsoftonline.com/<tenant-id>/v2.0, using the Directory (tenant) ID from step 3.
  6. Leave OIDC_USERNAME_CLAIM at its default. Entra ID provides preferred_username, so the email override from the Google guide is not needed here.

Always use your tenant ID in the issuer URL, not common or organizations. Those multi-tenant endpoints advertise the literal template {tenantid} as their issuer, which fails OIDC discovery validation.

WARNING

Entra ID's preferred_username tracks the user principal name, which changes when a user is renamed or moved to another tenant. SnapOtter reads the claim once, at first login, so the account keeps its original username afterwards. Logins keep working either way: returning users are matched by the token's stable subject, not by username.

Switching to Entra ID does not turn off password login. See Disabling local login.

Okta and other providers ​

Any provider that supports OIDC Discovery works the same way: create a confidential web client with your callback URL, then point OIDC_ISSUER_URL at the issuer. For Okta, create an app with the OIDC - OpenID Connect sign-in method and the Web Application type, then use your Okta domain as the issuer (e.g. https://your-company.okta.com).

SnapOtter does not map identity provider groups to roles. New SSO users get OIDC_DEFAULT_ROLE, admins change roles under Settings > Users, and requesting group claims through OIDC_SCOPES has no effect.

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).

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 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