GateKeeper is a full OIDC identity provider. Any application that supports OIDC can delegate authentication to it - Traefik Manager, Grafana, Jellyfin, Portainer, or any custom app.
What this means
Instead of each app managing its own login, they redirect users to GateKeeper. GateKeeper handles authentication (password, passkey, TOTP, email OTP) and returns a verified identity token. Apps never see credentials.
Endpoints
| Purpose | URL |
|---|---|
| Discovery | https://auth.example.com/.well-known/openid-configuration |
| Authorization | https://auth.example.com/authorize |
| Token | https://auth.example.com/oauth/token |
| Userinfo | https://auth.example.com/userinfo |
| JWKS (public keys) | https://auth.example.com/keys |
| Issuer | https://auth.example.com |
Apps that support OIDC discovery only need the discovery URL - everything else auto-configures.
Supported flow
Authorization code + PKCE only. Implicit flow and client credentials are not supported.
Supported scopes
| Scope | Claims returned |
|---|---|
openid | sub (user ID) |
profile | preferred_username (email) |
email | email, email_verified |
offline_access | Enables refresh tokens |
Registering a client
- Go to
/clientsand click New client - Enter a display name and optionally an icon URL (fetched and cached server-side at save time)
- Choose a client ID - lowercase, digits, dashes. Public and permanent.
- Click Generate for the client secret. Copy it - it is not shown again.
- Enter redirect URIs one per line. Must match exactly.
Configuring an app
Most apps work with just the discovery URL, client ID, and client secret:
Discovery URL: https://auth.example.com/.well-known/openid-configuration
Client ID: your-client-id
Client Secret: your-client-secret
Scopes: openid email profilePython (authlib)
from authlib.integrations.flask_client import OAuth
oauth = OAuth(app)
oauth.register(
name='gatekeeper',
server_metadata_url='https://auth.example.com/.well-known/openid-configuration',
client_id='myapp',
client_secret='your-client-secret',
client_kwargs={'scope': 'openid email profile'},
)Go (go-oidc)
provider, _ := oidc.NewProvider(ctx, "https://auth.example.com")
config := oauth2.Config{
ClientID: "myapp",
ClientSecret: "your-client-secret",
Endpoint: provider.Endpoint(),
RedirectURL: "https://myapp.example.com/callback",
Scopes: []string{oidc.ScopeOpenID, "email", "profile"},
}Login page branding
When a user arrives via /authorize, the login page shows the client's display name and icon. Direct /login access shows the GateKeeper logo.
Token lifetimes
| Token | Lifetime |
|---|---|
| Access token | 15 minutes |
| Refresh token | 30 days |
| ID token | 15 minutes |
Key rotation
Mobile and desktop apps
An application that is not a website receives its authorization code on a custom address such as app.immich:///oauth-callback instead of an https:// one.
Register that address as a redirect URI like any other. GateKeeper sees the custom scheme and treats the client as a native application, which is what allows the address to be used. Nothing needs to be switched on, and clients that only use http or https are unaffected.
A client may mix both. Registering a web address and a mobile address on the same client is the normal arrangement for an app that has a website and a phone app, and both work.
The redirect address is still matched exactly against the registered list, so a custom scheme widens the kind of address that is allowed and never the set of destinations. See Immich for a full example.
Tokens are signed with RS256. Keys rotate every 30 days automatically, checked hourly with no restart required. The previous key stays published for 48 hours so tokens issued just before rotation remain valid. See OIDC security.
Trusted devices
After a user passes 2FA, a 30-day trusted device cookie skips 2FA on return logins from the same device. Works for both OIDC and ForwardAuth flows.