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.
Find a file
2026-06-23 21:38:39 +00:00
.env.example Initial commit: discourse-oauth2-bridge 2026-06-23 21:13:40 +00:00
.gitignore Initial commit: discourse-oauth2-bridge 2026-06-23 21:13:40 +00:00
Dockerfile Initial commit: discourse-oauth2-bridge 2026-06-23 21:13:40 +00:00
package.json Initial commit: discourse-oauth2-bridge 2026-06-23 21:13:40 +00:00
README.md Add README 2026-06-23 21:38:39 +00:00
server.js Initial commit: discourse-oauth2-bridge 2026-06-23 21:13:40 +00:00

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:

  1. /authorize — receives an OAuth2 authorization request from Authentik, generates a DiscourseConnect SSO request, and redirects the user to Discourse to log in
  2. /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
  3. /token — exchanges the authorization code for an access token
  4. /userinfo — returns the authenticated user's profile to Authentik in OIDC-compatible format
  5. /.well-known/openid-configuration — OIDC discovery endpoint so Authentik can auto-configure itself
  6. /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 → search sso 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 your DISCOURSE_SECRET
  • Enable verbose sso logging while 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 .env file 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