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