Add README
This commit is contained in:
parent
a8442250e0
commit
c22c1537ff
1 changed files with 180 additions and 0 deletions
180
README.md
Normal file
180
README.md
Normal 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
|
||||||
Loading…
Reference in a new issue