CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 36s
Step 1 of the Scribe -> Grimoire rename: every capitalised "Scribe" in prose
becomes "Grimoire" ("Fabled Scribe" -> "Fabled Grimoire") across the UI,
the PWA manifest and page title, the MCP instructions and tool docstrings,
hook and skill text, docs and README, with the tests that assert on that
prose moving with it. Old migrations keep their wording.
Identifiers are untouched here: the package, plugin name, MCP server key,
hook file names, env vars, the .scribe marker and FabledScribe repo/image
names move in steps 2 and 3, along with prose that quotes them.
The sweep matched "Scribe" only as a whole word, so describe/subscribe and
friends are untouched. The longer name took the MCP instructions one
character over their 1,600 budget; two phrases are shortened to fit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.5 KiB
Markdown
96 lines
3.5 KiB
Markdown
# OAuth / OIDC SSO Setup
|
|
|
|
Grimoire supports single sign-on via any OpenID Connect provider.
|
|
This guide covers Authentik, but the same pattern works with Keycloak, Authelia, Zitadel, etc.
|
|
|
|
## 1. Create the provider in Authentik
|
|
|
|
1. Log in to the Authentik admin UI.
|
|
2. Go to **Applications → Providers → Create → OAuth2/OpenID Provider**.
|
|
3. Fill in:
|
|
- **Name:** `Grimoire` (or whatever you like)
|
|
- **Authorization flow:** your default authorization flow
|
|
- **Client type:** `Confidential`
|
|
- **Redirect URIs:** `https://your-fabled-domain/api/auth/oauth/callback`
|
|
*(must match `BASE_URL` exactly, including scheme and any path prefix)*
|
|
4. Note the generated **Client ID** and **Client Secret**.
|
|
5. Go to **Applications → Create**, give it a name, and bind it to the provider you just created.
|
|
6. Note the **Issuer URL** from the provider detail page — it looks like:
|
|
```
|
|
https://auth.example.com/application/o/fabled-assistant/
|
|
```
|
|
|
|
## 2. Configure Grimoire
|
|
|
|
Add the following environment variables to the `app` service in `docker-compose.yml`:
|
|
|
|
```yaml
|
|
services:
|
|
app:
|
|
environment:
|
|
# --- Required ---
|
|
OIDC_ISSUER: "https://auth.example.com/application/o/fabled-assistant/"
|
|
OIDC_CLIENT_ID: "abc123xyz"
|
|
OIDC_CLIENT_SECRET: "supersecret"
|
|
|
|
# --- Optional ---
|
|
# Scopes to request (default: "openid profile email")
|
|
# OIDC_SCOPES: "openid profile email"
|
|
|
|
# Disable local username/password login once SSO is working
|
|
# LOCAL_AUTH_ENABLED: "false"
|
|
|
|
# Make sure BASE_URL matches the redirect URI you registered
|
|
BASE_URL: "https://your-fabled-domain"
|
|
```
|
|
|
|
> **Docker Secrets alternative:** Use `OIDC_CLIENT_SECRET_FILE` pointing to a Docker secret file instead of `OIDC_CLIENT_SECRET`.
|
|
|
|
Rebuild and restart:
|
|
|
|
```bash
|
|
docker compose up --build -d
|
|
```
|
|
|
|
## 3. Verify
|
|
|
|
1. Open `/api/auth/status` — it should return:
|
|
```json
|
|
{ "oauth_enabled": true, "local_auth_enabled": true, ... }
|
|
```
|
|
2. Go to the login page — you should see a **"Login with [Provider]"** button.
|
|
3. Click it → redirected to provider → authenticate → redirected back → logged in.
|
|
4. Check `/api/auth/me` to confirm your user record.
|
|
|
|
## 4. Account Linking
|
|
|
|
When a user logs in via OAuth for the first time, Grimoire checks in this order:
|
|
|
|
1. **Existing OAuth sub** — returns that user immediately.
|
|
2. **Matching email** — if a local account already exists with the same email, the OAuth identity is linked to it automatically. The user retains all their notes and tasks.
|
|
3. **New user** — a fresh account is created. Username defaults to `preferred_username` from the provider; if taken, `_2`, `_3`, etc. is appended.
|
|
|
|
## 5. Disable Local Login (Optional)
|
|
|
|
Once everyone is using SSO:
|
|
|
|
```yaml
|
|
LOCAL_AUTH_ENABLED: "false"
|
|
```
|
|
|
|
The backend will reject any `POST /api/auth/login` or `POST /api/auth/register` request with a 403. The login page will only show the SSO button.
|
|
|
|
> **Warning:** Make sure at least one account has been linked via OAuth before disabling local login, or you will be locked out.
|
|
|
|
## 6. Other Providers
|
|
|
|
| Provider | Issuer URL format |
|
|
|----------|------------------|
|
|
| Authentik | `https://auth.example.com/application/o/<app-slug>/` |
|
|
| Keycloak | `https://keycloak.example.com/realms/<realm>` |
|
|
| Authelia | `https://auth.example.com` |
|
|
| Zitadel | `https://your-instance.zitadel.cloud` |
|
|
| Google | `https://accounts.google.com` |
|
|
|
|
The OIDC discovery endpoint (`<issuer>/.well-known/openid-configuration`) must be publicly reachable from the Grimoire container (server-to-server call at login time).
|