Configuring Single Sign-On (OIDC)
Set up enterprise authentication with providers like Authentik, Keycloak, Auth0, or Okta.
Scanopy supports OpenID Connect (OIDC) for enterprise authentication with providers like Authentik, Keycloak, Auth0, Okta, PocketID, and others.
Quick Start
Option A: TOML File (recommended for complex setups)
- Copy
oidc.toml.exampletooidc.toml - Configure your provider settings (see examples below)
- Mount the file in docker-compose
- Restart the server
Option B: Environment Variable
- Set
SCANOPY_OIDC_PROVIDERSwith your provider config (see format below) - Restart the server
See Environment Variable Configuration for details.
Configuration File Format
[[oidc_providers]]
name = "Provider Name" # Display name in UI
slug = "provider-slug" # Used in callback URL (lowercase, no spaces)
logo = "https://..." # Optional: logo URL for UI
issuer_url = "https://..." # Provider's OIDC issuer URL
client_id = "your-client-id"
client_secret = "your-client-secret"You can configure multiple providers by adding multiple [[oidc_providers]] sections.
Environment Variable Configuration
Instead of using a TOML file, you can configure OIDC providers using the SCANOPY_OIDC_PROVIDERS environment variable.
Important: This uses TOML-like syntax, NOT JSON. Use key="value" with equals signs, not "key":"value" with colons.
Shell / .env Files
For shell scripts, .env files, or tools like Kamal that use shell-style secrets:
SCANOPY_OIDC_PROVIDERS='[{name="Authentik",slug="authentik",issuer_url="https://auth.example.com/application/o/scanopy",client_id="your-client-id",client_secret="your-client-secret"}]'Requirements:
- Single quotes around the entire value
- No spaces after commas
- No newlines (must be a single line)
For multiple providers:
SCANOPY_OIDC_PROVIDERS='[{name="Authentik",slug="authentik",issuer_url="https://auth.example.com/application/o/scanopy",client_id="id1",client_secret="secret1"},{name="Keycloak",slug="keycloak",issuer_url="https://keycloak.example.com/realms/main",client_id="id2",client_secret="secret2"}]'YAML Files (Docker Compose, Kubernetes)
For YAML-based configuration, use the >- block scalar to handle the format cleanly:
environment:
SCANOPY_OIDC_PROVIDERS: >-
[{name="Authentik",slug="authentik",issuer_url="https://auth.example.com/application/o/scanopy",client_id="your-client-id",client_secret="your-client-secret"}]The >- folds the value into a single line and removes trailing newlines. You can also format it for readability:
environment:
SCANOPY_OIDC_PROVIDERS: >-
[{
name="Authentik",
slug="authentik",
issuer_url="https://auth.example.com/application/o/scanopy",
client_id="your-client-id",
client_secret="your-client-secret"
}]Use >- (with the hyphen), not just >. The hyphen strips trailing newlines which prevents parsing errors.
Each provider object supports these fields:
| Field | Required | Description |
|---|---|---|
name | Yes | Display name shown in UI |
slug | Yes | URL-safe identifier (lowercase, no spaces) |
issuer_url | Yes | OIDC provider's issuer URL |
client_id | Yes | OAuth2 client ID |
client_secret | Yes | OAuth2 client secret |
logo | No | Logo URL for UI display |
Docker Compose Example
services:
scanopy-server:
image: ghcr.io/scanopy/scanopy/server:latest
environment:
SCANOPY_PUBLIC_URL: https://scanopy.example.com
SCANOPY_OIDC_PROVIDERS: >-
[{name="Authentik",slug="authentik",issuer_url="https://auth.example.com/application/o/scanopy",client_id="your-client-id",client_secret="your-client-secret"}]This method is useful when:
- You want to keep all configuration in environment variables
- You're using container orchestration that injects secrets via env vars
- You prefer not to mount additional config files
Note: Environment variables take precedence over the TOML file if both are configured.
Callback URL Format
Configure this URL in your OIDC provider's redirect/callback settings:
http://your-scanopy-domain:60072/api/auth/oidc/{slug}/callbackReplace {slug} with the slug value from your oidc.toml. For example, if slug = "authentik":
http://scanopy.local:60072/api/auth/oidc/authentik/callbackRequired scopes: openid, email, profile (profile is optional but recommended)
Docker Compose Setup
Add the following volume mount to your scanopy-server service:
services:
scanopy-server:
image: ghcr.io/scanopy/scanopy/server:latest
volumes:
- ./oidc.toml:/oidc.toml:ro
# ... rest of configProvider Examples
Authentik
-
Create Application in Authentik Admin → Applications → Create:
- Name:
Scanopy - Slug:
scanopy - Provider: Create a new OAuth2/OpenID Provider
- Name:
-
Configure Provider:
- Name:
Scanopy OIDC - Authorization flow:
default-provider-authorization-implicit-consent - Client type:
Confidential - Redirect URIs:
http://your-scanopy:60072/api/auth/oidc/authentik/callback - Copy the Client ID and Client Secret
- Name:
-
Find your Issuer URL:
- Go to Providers → your provider → OpenID Configuration Issuer
- Usually:
https://auth.yourdomain.com/application/o/scanopy/ - Important: Remove trailing slash if present (see Common Issues)
-
Configure oidc.toml:
[[oidc_providers]]
name = "Authentik"
slug = "authentik"
logo = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authentik.svg"
issuer_url = "https://auth.yourdomain.com/application/o/scanopy"
client_id = "your-client-id"
client_secret = "your-client-secret"Keycloak
-
Create Client in Keycloak Admin → Clients → Create:
- Client ID:
scanopy - Client type:
OpenID Connect - Client authentication:
On
- Client ID:
-
Configure Client Settings:
- Valid redirect URIs:
http://your-scanopy:60072/api/auth/oidc/keycloak/callback - Web origins:
http://your-scanopy:60072
- Valid redirect URIs:
-
Get Credentials:
- Go to Credentials tab
- Copy Client Secret
-
Configure oidc.toml:
[[oidc_providers]]
name = "Keycloak"
slug = "keycloak"
logo = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keycloak.svg"
issuer_url = "https://keycloak.yourdomain.com/realms/your-realm"
client_id = "scanopy"
client_secret = "your-client-secret"PocketID
-
Create OIDC Client in PocketID:
- Go to OIDC Clients → Add Client
- Name:
Scanopy - Callback URLs:
http://your-scanopy:60072/api/auth/oidc/pocketid/callback
-
Copy Credentials:
- Client ID
- Client Secret
-
Configure oidc.toml:
[[oidc_providers]]
name = "PocketID"
slug = "pocketid"
logo = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pocketid.svg"
issuer_url = "https://pocketid.yourdomain.com"
client_id = "your-client-id"
client_secret = "your-client-secret"Auth0
-
Create Application in Auth0 Dashboard → Applications → Create:
- Type:
Regular Web Application - Name:
Scanopy
- Type:
-
Configure Application Settings:
- Allowed Callback URLs:
http://your-scanopy:60072/api/auth/oidc/auth0/callback - Allowed Web Origins:
http://your-scanopy:60072
- Allowed Callback URLs:
-
Configure oidc.toml:
[[oidc_providers]]
name = "Auth0"
slug = "auth0"
logo = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/auth0.svg"
issuer_url = "https://your-tenant.auth0.com"
client_id = "your-client-id"
client_secret = "your-client-secret"Okta
-
Create App Integration in Okta Admin → Applications → Create:
- Sign-in method:
OIDC - OpenID Connect - Application type:
Web Application
- Sign-in method:
-
Configure Settings:
- Sign-in redirect URIs:
http://your-scanopy:60072/api/auth/oidc/okta/callback - Sign-out redirect URIs:
http://your-scanopy:60072
- Sign-in redirect URIs:
-
Configure oidc.toml:
[[oidc_providers]]
name = "Okta"
slug = "okta"
logo = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/okta.svg"
issuer_url = "https://your-org.okta.com"
client_id = "your-client-id"
client_secret = "your-client-secret"Authelia
identity_providers:
oidc:
clients:
- client_id: 'scanopy'
client_name: 'Scanopy'
client_secret: { { secret "/run/secrets/authelia_scanopy_oidc" | msquote } }
public: false
authorization_policy: 'two_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://scanopy.YOURDOMAIN/api/auth/oidc/authelia/callback'
scopes:
- 'openid'
- 'email'
- 'profile'
response_types:
- 'code'
grant_types:
- 'authorization_code'
access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_basic'
consent_mode: 'auto'
pre_configured_consent_duration: '1M'[[oidc_providers]]
name = "Authelia"
slug = "authelia"
logo = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg"
issuer_url = "https://auth.YOURDOMAIN"
client_id = "scanopy"
client_secret = "YOURSUPERSECRET"Linking OIDC to Existing Accounts
If OIDC is enabled, users can link it to their existing email/password account:
- Log in with your existing email and password
- Go to Account Settings (click the user icon in top right)
- Click Link under your OIDC provider
- Complete the authentication flow with your provider
- Your account is now linked — you can log in with either method
Unlinking: You can unlink OIDC at any time from Account Settings, but you'll need to have a password set first.
Common Issues
"Unexpected issuer URI" error
Failed to generate auth URL: Validation error: unexpected issuer URI
`https://auth.example.com/app/` (expected `https://auth.example.com/app`)Cause: Trailing slash mismatch between your config and what the provider returns.
Solution: Try both with and without trailing slash in issuer_url. The value must exactly match what your provider returns in its .well-known/openid-configuration.
To check what your provider expects:
curl https://your-provider/.well-known/openid-configuration | jq .issuer"Invalid redirect URI" error
Cause: The callback URL in your provider doesn't match what Scanopy sends.
Solution: Ensure the redirect URI in your provider exactly matches:
http://your-scanopy:60072/api/auth/oidc/{slug}/callbackCommon mistakes:
- Wrong protocol (http vs https)
- Wrong port
- Wrong slug (must match oidc.toml)
- Missing
/callbackat the end
OIDC button not appearing in UI
Causes:
- oidc.toml file not mounted in Docker (if using file-based config)
- oidc.toml has syntax errors
SCANOPY_OIDC_PROVIDERShas invalid JSON (if using env var config)- Server not restarted after adding config
Solution:
- If using TOML file: Verify the volume mount exists in docker-compose.yml and validate TOML syntax
- If using env var: Validate JSON syntax — use
echo $SCANOPY_OIDC_PROVIDERS | jq .to check - Restart with
docker compose restart scanopy-server - Check server logs:
docker logs scanopy-server
"Connection refused" when authenticating
Cause: Scanopy server can't reach your OIDC provider.
Solutions:
- Ensure the provider URL is reachable from the server container
- If provider is internal, ensure Docker can resolve the hostname
- Add provider to Docker's extra_hosts if needed:
extra_hosts: - 'auth.internal:192.168.1.100'