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

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

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