| .env.example | ||
| .gitignore | ||
| Dockerfile | ||
| package.json | ||
| README.md | ||
| server.js | ||
discourse-oauth2-bridge
A lightweight Node.js bridge that allows Authentik (and other OIDC-compatible identity providers) to use Discourse as its authentication backend via DiscourseConnect.
The problem it solves
Discourse has a built-in SSO provider called DiscourseConnect, which lets other apps authenticate against it. However, most modern apps (Nextcloud, Outline, Invoice Ninja, etc.) expect OIDC or OAuth2, not DiscourseConnect. This bridge sits between Authentik and Discourse, translating DiscourseConnect into a standard OAuth2/OIDC interface that Authentik can consume.
App (e.g. Nextcloud, Outline)
│
│ OIDC
▼
Authentik
│
│ OAuth2 (via this bridge)
▼
discourse-oauth2-bridge ←── this repo
│
│ DiscourseConnect
▼
Discourse ←── source of truth: users, groups, permissions
User accounts, group memberships and permissions are all managed in Discourse. Any app connected via Authentik inherits them automatically.
How it works
The bridge implements a minimal OAuth2 authorization code flow:
/authorize— receives an OAuth2 authorization request from Authentik, generates a DiscourseConnect SSO request, and redirects the user to Discourse to log in/callback— Discourse redirects back here after login; the bridge validates the signature, extracts user info (username, email, groups, admin status), and issues a short-lived authorization code/token— exchanges the authorization code for an access token/userinfo— returns the authenticated user's profile to Authentik in OIDC-compatible format/.well-known/openid-configuration— OIDC discovery endpoint so Authentik can auto-configure itself/health— health check endpoint used by Docker
Tokens and auth codes are stored in memory and expire after 10 minutes. The bridge is stateless beyond that — no database required.
Requirements
- A running Discourse instance with DiscourseConnect provider enabled (
/admin/site_settings→ searchsso provider) - A running Authentik instance
- Docker (recommended) or Node.js 20+
Setup
1. Configure Discourse
In your Discourse admin settings:
- Enable
enable sso provider - Set
sso secret— this becomes yourDISCOURSE_SECRET - Enable
verbose sso loggingwhile testing (optional but helpful)
2. Configure the bridge
Copy .env.example to .env and fill in your values:
cp .env.example .env
nano .env
# Port the bridge listens on inside Docker
PORT=3000
# Your Discourse instance
DISCOURSE_URL=https://discourse.example.com
DISCOURSE_SECRET=CHANGE_ME
# The URL this bridge is accessible at (used in redirects)
BRIDGE_URL=https://auth-bridge.example.com
# OAuth2 credentials — you will set these in Authentik (step 3)
OAUTH2_CLIENT_ID=CHANGE_ME
OAUTH2_CLIENT_SECRET=CHANGE_ME
Generate a strong secret for OAUTH2_CLIENT_SECRET:
openssl rand -hex 32
3. Configure Authentik
In Authentik, create a new Generic OAuth2 / OIDC provider:
| Setting | Value |
|---|---|
| Authorization URL | https://auth-bridge.example.com/authorize |
| Token URL | https://auth-bridge.example.com/token |
| Userinfo URL | https://auth-bridge.example.com/userinfo |
| Client ID | your chosen OAUTH2_CLIENT_ID |
| Client Secret | your chosen OAUTH2_CLIENT_SECRET |
Or use the OIDC discovery URL and let Authentik configure itself:
https://auth-bridge.example.com/.well-known/openid-configuration
4. Run with Docker
docker build -t discourse-oauth2-bridge .
docker run -d \
--name discourse-oauth2-bridge \
--env-file .env \
-p 3000:3000 \
discourse-oauth2-bridge
Or with Docker Compose — see the example below.
5. Verify
curl https://auth-bridge.example.com/health
# → {"status":"ok"}
curl https://auth-bridge.example.com/.well-known/openid-configuration
# → OIDC discovery document
Docker Compose example
This is the configuration used on tobiaseigen.org, where the bridge runs alongside Authentik on a mailcow server, sharing the mailcow Docker network:
services:
discourse-bridge:
build: .
container_name: authentik-discourse-bridge-1
restart: unless-stopped
env_file: .env
networks:
mailcow-network:
ipv4_address: 172.22.1.247 # adjust to a free IP on your network
networks:
mailcow-network:
external: true
name: mailcowdockerized_mailcow-network
User info claims
The /userinfo endpoint returns the following claims:
| Claim | Source |
|---|---|
sub |
Discourse external_id |
preferred_username |
Discourse username |
name |
Discourse full name |
email |
Discourse email |
picture |
Discourse avatar URL |
groups |
Discourse group memberships (comma-separated list) |
discourse_admin |
true if Discourse admin |
discourse_moderator |
true if Discourse moderator |
Authentik can use groups claims to map Discourse group membership to Authentik groups, which downstream apps can then use for access control.
Security notes
- Auth codes, access tokens and pending auth state are stored in memory only — they do not survive a container restart
- All tokens expire after 10 minutes
- The bridge validates Discourse's HMAC-SHA256 signature on every callback before trusting any user data
- The container runs as a non-root user (
bridge) - Keep your
.envfile private — it contains secrets. It is excluded from this repo via.gitignore
Background
This bridge was developed for the Digitally Sovereign self-hosting project, where Discourse is used as the single source of truth for user identity and access control across a suite of self-hosted tools. See the forum for background and discussion.
License
MIT