Discourse + SSO: Implement Single Sign-On with OAuth2 and OIDC
Managing separate logins for your community forum is a headache nobody asked for. Your users already have accounts in your main app, your CRM, or your identity provider. Making them create yet another password for Discourse is friction you can eliminate.
Discourse ships with solid SSO support out of the box. You have three main options: OpenID Connect (bundled with core), OAuth2 Basic (plugin), and DiscourseConnect (Discourse's native protocol). Each has tradeoffs depending on your existing auth infrastructure.
OpenID Connect: The Modern Standard
If your identity provider supports OIDC (Keycloak, Auth0, Okta, Azure AD), this is the cleanest path. The plugin ships with Discourse core, so there's nothing to install.
Head to Admin → Settings → Login and configure these values:
openid_connect_enabled: true
openid_connect_discovery_document: https://your-idp.com/.well-known/openid-configuration
openid_connect_client_id: discourse-community
openid_connect_client_secret: your-secret-here
openid_connect_authorize_scope: openid profile email
The discovery document does the heavy lifting. Discourse automatically fetches your provider's endpoints for authorization, token exchange, and user info.
For Keycloak specifically, your discovery URL looks like:
https://keycloak.example.com/realms/your-realm/.well-known/openid-configuration
Set your redirect URI in your identity provider to:
https://forum.example.com/auth/oidc/callback
Restart Discourse and your OIDC login button appears on the login page.
OAuth2 Basic: For Non-Standard Providers
Some systems expose OAuth2 but skip the OIDC layer. The OAuth2 Basic plugin handles these cases when you have a JSON endpoint that returns user data.
Install the plugin by adding to your app.yml:
hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone https://github.com/discourse/discourse-oauth2-basic.git
Rebuild and configure:
oauth2_enabled: true
oauth2_client_id: your-client-id
oauth2_client_secret: your-secret
oauth2_authorize_url: https://your-app.com/oauth/authorize
oauth2_token_url: https://your-app.com/oauth/token
oauth2_user_json_url: https://your-app.com/api/user
oauth2_json_user_id_path: id
oauth2_json_username_path: username
oauth2_json_email_path: email
The _path settings map JSON fields from your user endpoint to Discourse fields. If your API returns nested data like {"user": {"email": "..."}}, use user.email as the path.
DiscourseConnect: Full Control
When you need complete control over the authentication flow, DiscourseConnect (formerly Discourse SSO) lets your application be the identity source. Discourse redirects users to your endpoint, you validate them, and send back signed user data.
Enable it in Admin → Settings → Login:
discourse_connect_url: https://your-app.com/discourse/sso
discourse_connect_secret: generate-a-strong-secret-here
Your endpoint receives a sso parameter (base64-encoded payload) and a sig (HMAC-SHA256 signature). Validate the signature, authenticate the user however you want, then redirect back with user data.
Here's a Node.js implementation:
const crypto = require('crypto');
app.get('/discourse/sso', (req, res) => {
const { sso, sig } = req.query;
const secret = process.env.DISCOURSE_SSO_SECRET;
// Verify signature
const expected = crypto.createHmac('sha256', secret)
.update(sso).digest('hex');
if (sig !== expected) return res.status(403).send('Invalid signature');
// Decode payload and get nonce
const payload = Buffer.from(sso, 'base64').toString();
const nonce = new URLSearchParams(payload).get('nonce');
// Build response with your authenticated user
const user = req.session.user; // Your auth system
const responsePayload = new URLSearchParams({
nonce,
email: user.email,
external_id: user.id,
username: user.username,
name: user.fullName,
admin: user.isAdmin ? 'true' : 'false'
}).toString();
const responseBase64 = Buffer.from(responsePayload).toString('base64');
const responseSig = crypto.createHmac('sha256', secret)
.update(responseBase64).digest('hex');
res.redirect(`https://forum.example.com/session/sso_login?sso=${responseBase64}&sig=${responseSig}`);
});
DiscourseConnect also supports group sync. Add add_groups and remove_groups parameters to automatically manage forum groups based on your application's roles.
Syncing User Data
All three methods support ongoing sync. Enable these settings to keep user profiles current:
sso_overrides_email: true
sso_overrides_username: true
sso_overrides_name: true
sso_overrides_avatar: true
For group synchronization with OIDC, map your provider's groups claim:
openid_connect_claims_groups: groups
Troubleshooting
Login redirects but returns to login page: Check your redirect URI matches exactly what's configured in your identity provider. Trailing slashes matter.
User created but email not verified: Enable sso_overrides_email and ensure your provider includes email verification status.
Groups not syncing: Verify your identity provider includes the groups claim in the token. Use jwt.io to inspect your tokens during testing.
CORS errors on token exchange: This happens when the browser tries to call your token endpoint directly. Ensure your OAuth2 flow uses server-side token exchange, not implicit flow.
Avatar not updating: The avatar URL must be publicly accessible. Discourse fetches it server-side and caches aggressively. Clear the cache with rake avatars:refresh.
Session timeout mismatch: If users get logged out of Discourse while still logged into your main app, align the session durations. Set maximum_session_age in Discourse to match your identity provider's token lifetime.
Multiple SSO providers: Discourse supports enabling multiple authentication methods simultaneously. Users can link accounts later if they initially sign up with a different provider.
Security Considerations
Never store your SSO secret in version control. Use environment variables or a secrets manager. For DiscourseConnect, generate secrets with at least 32 characters of randomness:
openssl rand -hex 32
Enable discourse_connect_overrides_groups only if you trust your identity provider completely. This setting allows external systems to grant admin access to users.
Consider setting require_activation to false when using SSO. Your identity provider already verified the email, so double-verification adds friction without security benefit.
Deploy Discourse with SSO Ready
Setting up Discourse with proper SSO configuration takes careful planning. Deploy Discourse on Elestio and get a production-ready instance with automated backups and updates. Focus on configuring your auth flow instead of managing infrastructure.
Thanks for reading!