Add README

This commit is contained in:
root 2026-06-23 21:38:39 +00:00
parent a8442250e0
commit c22c1537ff

180
README.md Normal file
View file

@ -0,0 +1,180 @@
# discourse-oauth2-bridge
A lightweight Node.js bridge that allows [Authentik](https://goauthentik.io/) (and other OIDC-compatible identity providers) to use [Discourse](https://discourse.org/) as its authentication backend via [DiscourseConnect](https://meta.discourse.org/t/discourseconnect-official-sso-solution-for-discourse/13045).
## 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](https://goauthentik.io/) 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:
```bash
cp .env.example .env
nano .env
```
```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`:
```bash
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
```bash
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
```bash
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](https://tobiaseigen.org), where the bridge runs alongside Authentik on a mailcow server, sharing the mailcow Docker network:
```yaml
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](https://discourse.tobiaseigen.org) 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