Appearance
OAuth / SSO
Postmill supports generic OpenID Connect (OIDC) providers for user authentication. This lets self-hosters integrate with Authentik, Keycloak, Authelia, Dex, Supabase, or any OIDC-compliant identity provider. SAML is not supported — OIDC only.
Admin-managed login providers
Login providers are configured in the separate administration app (a distinct repository — this repo ships no /admin frontend and no login-provider write API), stored platform-wide in the AuthProviderConfig table with client ID/secret encrypted at rest (one config per provider: Google, GitHub, or generic OIDC). For each provider you can set:
- Enabled — whether the provider's button appears on the login page
- Client ID / Client Secret — encrypted; never returned to the browser after save
- OIDC endpoints (generic provider only) — authorization, token, and userinfo URLs
- Scopes — defaults to
openid profile email - Display name — the login button label (e.g. "Sign in with Supabase")
This repo only reads AuthProviderConfig. The login page reads GET /auth/providers, which merges the env-derived provider list with the enabled DB configs: email/password (LOCAL) is always present, env-configured providers stay listed, and a DB config wins per provider key (its display name and credentials take precedence over the env ones for that provider only).
Env fallback (the chicken-and-egg): when no enabled DB config exists for a provider, the env vars below are used instead — so a fresh deployment can offer SSO logins before anything is configured in the database. Email/password (LOCAL) login is always available regardless of provider config (subject to DISABLE_REGISTRATION).
OAuth logins also import the user's display name and avatar from the provider; accounts without a provider picture fall back to a Gravatar generated from their email, then to initials.
Generic OIDC setup
1. Create an OIDC application in your provider
Create a new OIDC client in your identity provider. You will need:
- Client ID and Client Secret — generated by your provider
- Redirect URI —
https://<your-postmill-domain>/settings - Scopes — typically
openid profile email
Example redirect URI: https://postmill.example.com/settings
2. Configure the provider
Preferred: in the administration app, enable the Generic OIDC provider and enter the client ID/secret, the three endpoints, scopes, and a display name. No redeploy needed.
Or via env vars (bootstrap fallback) — set these in your .env file or Docker Compose environment; they apply only while no enabled DB config exists for the provider:
yaml
# Required for any self-hosted deployment
IS_GENERAL: 'true'
# Enable generic OIDC
POSTMILL_GENERIC_OAUTH: 'true'
# Provider endpoints
POSTMILL_OAUTH_AUTH_URL: 'https://auth.example.com/application/o/authorize'
POSTMILL_OAUTH_TOKEN_URL: 'https://auth.example.com/application/o/token'
POSTMILL_OAUTH_USERINFO_URL: 'https://auth.example.com/application/o/userinfo'
# Client credentials
POSTMILL_OAUTH_CLIENT_ID: 'your-client-id'
POSTMILL_OAUTH_CLIENT_SECRET: 'your-client-secret'
# Login button appearance
NEXT_PUBLIC_POSTMILL_OAUTH_DISPLAY_NAME: 'Authentik'
NEXT_PUBLIC_POSTMILL_OAUTH_LOGO_URL: 'https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/authentik.png'3. Optional: Override OIDC scopes
The default scopes are openid profile email. Override if your provider requires different or additional scopes:
yaml
POSTMILL_OAUTH_SCOPE: 'openid profile email groups'4. Redeploy
Restart the application after setting these variables. The login page will show a button with NEXT_PUBLIC_POSTMILL_OAUTH_DISPLAY_NAME and NEXT_PUBLIC_POSTMILL_OAUTH_LOGO_URL.
Required env vars
| Variable | Default | Purpose |
|---|---|---|
IS_GENERAL | — | Must be true for self-hosted deployments |
POSTMILL_GENERIC_OAUTH | false | Set to true to enable generic OIDC login |
POSTMILL_OAUTH_AUTH_URL | — | Authorization endpoint |
POSTMILL_OAUTH_TOKEN_URL | — | Token endpoint |
POSTMILL_OAUTH_USERINFO_URL | — | Userinfo endpoint |
POSTMILL_OAUTH_CLIENT_ID | — | Client ID from your OIDC provider |
POSTMILL_OAUTH_CLIENT_SECRET | — | Client secret from your OIDC provider |
NEXT_PUBLIC_POSTMILL_OAUTH_DISPLAY_NAME | — | Login button label |
NEXT_PUBLIC_POSTMILL_OAUTH_LOGO_URL | — | Login button icon URL |
Optional env vars
| Variable | Default | Purpose |
|---|---|---|
POSTMILL_OAUTH_SCOPE | openid profile email | OIDC scopes to request |
Provider-specific examples
Authentik
yaml
IS_GENERAL: 'true'
POSTMILL_GENERIC_OAUTH: 'true'
POSTMILL_OAUTH_AUTH_URL: 'https://authentik.example.com/application/o/authorize/'
POSTMILL_OAUTH_TOKEN_URL: 'https://authentik.example.com/application/o/token/'
POSTMILL_OAUTH_USERINFO_URL: 'https://authentik.example.com/application/o/userinfo/'
POSTMILL_OAUTH_CLIENT_ID: '<your-client-id>'
POSTMILL_OAUTH_CLIENT_SECRET: '<your-client-secret>'
NEXT_PUBLIC_POSTMILL_OAUTH_DISPLAY_NAME: 'Authentik'
NEXT_PUBLIC_POSTMILL_OAUTH_LOGO_URL: 'https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/authentik.png'In Authentik:
- Create a new Application with type
OAuth2/OpenID - Set the redirect URI to
https://<your-domain>/settings - Under the Provider settings, note the client ID and secret
- Under Advanced protocol settings, ensure the signing key and subject mode are set
Keycloak
yaml
POSTMILL_OAUTH_AUTH_URL: 'https://keycloak.example.com/realms/<realm>/protocol/openid-connect/auth'
POSTMILL_OAUTH_TOKEN_URL: 'https://keycloak.example.com/realms/<realm>/protocol/openid-connect/token'
POSTMILL_OAUTH_USERINFO_URL: 'https://keycloak.example.com/realms/<realm>/protocol/openid-connect/userinfo'Authelia
yaml
POSTMILL_OAUTH_AUTH_URL: 'https://auth.example.com/api/oidc/authorization'
POSTMILL_OAUTH_TOKEN_URL: 'https://auth.example.com/api/oidc/token'
POSTMILL_OAUTH_USERINFO_URL: 'https://auth.example.com/api/oidc/userinfo'Dex
yaml
POSTMILL_OAUTH_AUTH_URL: 'https://dex.example.com/auth'
POSTMILL_OAUTH_TOKEN_URL: 'https://dex.example.com/token'
POSTMILL_OAUTH_USERINFO_URL: 'https://dex.example.com/userinfo'GitHub login
In addition to generic OIDC, Postmill supports GitHub OAuth as a login provider. Configure it in the administration app (preferred), or via the bootstrap env vars:
yaml
GITHUB_CLIENT_ID: '<your-github-oauth-app-client-id>'
GITHUB_CLIENT_SECRET: '<your-github-oauth-app-client-secret>'Create a GitHub OAuth App at Settings -> Developer settings -> OAuth Apps with the callback URL set to https://<your-domain>/settings (the adapter requests the user:email scope).
Google login
Google OAuth login is also supported. Configure it in the administration app (preferred), or use YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRET as the bootstrap env credentials. These variables are dual-use: they are also the platform channel app for YouTube posting — setting them gives every organization one-click YouTube Connect (per-org Google/YouTube/GMB apps via Settings → Channels still win for channel posting). Setup details: Platform Channel Apps → Google — YouTube.
Facebook login
Facebook OAuth login reuses the platform channel app — there is no separate login credential set. Set the flag and the channel credentials:
yaml
FACEBOOK_SSO_ENABLED: 'true'
FACEBOOK_APP_ID: '<your-meta-app-id>'
FACEBOOK_APP_SECRET: '<your-meta-app-secret>'The Meta app needs the consumer Facebook Login product for the login button to work — the "Facebook Login for Business" Configuration-ID flow is Pages-only and cannot log users in. Login requests only public_profile,email; if the user denies the email permission, Postmill mints a synthetic address (fb_<id>@facebook.login.postmill.local) that is skipped by newsletter/welcome emails. Callback to register: https://<your-domain>/integrations/social/facebook.
X login
X OAuth login also reuses the platform channel app, but over a separate OAuth 2.0 + PKCE flow (channel posting is OAuth 1.0a — enable both in the app's User authentication settings):
yaml
X_SSO_ENABLED: 'true'
X_API_KEY: '<your-api-key>'
X_API_SECRET: '<your-api-secret>'Login requests only the users.read scope, which returns no email address — every X SSO account gets a synthetic address (x_<id>@x.login.postmill.local), skipped by newsletter/welcome emails. Callback to register: https://<your-domain>/integrations/social/x.
LinkedIn login
LinkedIn OAuth login reuses the platform channel app via LinkedIn's OpenID Connect flow (openid profile email against /v2/userinfo):
yaml
LINKEDIN_SSO_ENABLED: 'true'
LINKEDIN_CLIENT_ID: '<your-client-id>'
LINKEDIN_CLIENT_SECRET: '<your-client-secret>'The app needs the Sign In with LinkedIn using OpenID Connect product enabled (the channel flow already requires it). Callback to register: https://<your-domain>/integrations/social/linkedin.
No account linking (all SSO providers): logins match users by (provider, provider-user-id) only. The same person signing in via two providers — even with the same real email — gets separate User + Org accounts; identities are never merged.
Login callback routes
OAuth callbacks do not land on a single /auth/callback endpoint. Each login provider redirects back to its own path carrying state=login, and the frontend proxy (apps/frontend/src/proxy.ts) forwards it to /auth?…&provider=<NAME>:
| Provider | Callback path to register |
|---|---|
https://<your-domain>/integrations/social/youtube?state=login (register the path without the query) | |
https://<your-domain>/integrations/social/facebook | |
| X | https://<your-domain>/integrations/social/x |
https://<your-domain>/integrations/social/linkedin | |
| GitHub | https://<your-domain>/settings |
| Generic OIDC | https://<your-domain>/settings |
The dual-use paths are shared with channel OAuth: channel-connect callbacks hit the same /integrations/social/<provider> paths without state=login and pass straight through to the channel flow. Ensure your reverse proxy passes these paths (and /settings) through to the frontend without modification.
Disable registration
To restrict who can sign up, set DISABLE_REGISTRATION=true. New users cannot self-register via LOCAL (email/password) or any OAuth provider (Google, GitHub, Facebook, X, LinkedIn) — with two exceptions: the very first user of an empty instance can always register (so the operator can bootstrap), and GENERIC OIDC sign-ins are exempt (users authenticated by your identity provider still provision). Existing users can still log in. Create new users through the Teams settings page.
Related
- Configuration — full env var reference including
IS_GENERAL, GitHub, and SSO vars - Platform Channel Apps — the shared channel-OAuth apps the Facebook/X/LinkedIn/Google logins dual-use
- Security — OAuth 2.0 / PKCE hardening, open-redirect allowlisting
Verified against v1.0.0