OIDC Setup
Configuring OpenID Connect authentication for self-hosted Scanopy.
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'