Files
FabledScribe/docs/oauth-setup.md
T
bvandeusen b37e15d59a Add Authentik OAuth/OIDC SSO, email change, and setup docs
Phase 18 changes:

OAuth/OIDC SSO (Authorization Code + PKCE):
- alembic/versions/0015_add_oauth_fields.py: add oauth_sub UNIQUE column,
  drop NOT NULL on password_hash
- src/fabledassistant/services/oauth.py: OIDC discovery (cached), build_auth_url,
  exchange_code, get_userinfo, find_or_create_oauth_user (sub→email auto-link→create)
- src/fabledassistant/routes/auth.py: GET /api/auth/oauth/login and
  GET /api/auth/oauth/callback; LOCAL_AUTH_ENABLED guards on login/register;
  /api/auth/status now returns oauth_enabled + local_auth_enabled
- src/fabledassistant/config.py: OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET,
  OIDC_SCOPES, LOCAL_AUTH_ENABLED, oidc_enabled() classmethod
- src/fabledassistant/models/user.py: password_hash nullable, oauth_sub field,
  has_password bool in to_dict()
- src/fabledassistant/services/auth.py: create_user accepts password=None +
  oauth_sub kwarg; authenticate returns None for OAuth-only users;
  add get_user_by_oauth_sub, link_oauth_sub, update_user_email
- frontend: AuthStatus + User types updated; auth store exposes oauthEnabled +
  localAuthEnabled; LoginView shows SSO button / hides password form accordingly

Email change:
- PUT /api/auth/email: requires password confirmation for local-auth users,
  skips check for OAuth-only users; enforces email uniqueness
- SettingsView.vue: new Email Address section pre-filled with current email,
  updates authStore.user in-place on success

Docs:
- docs/oauth-setup.md: step-by-step Authentik provider setup, example
  docker-compose env vars, account linking explanation, per-provider issuer URL table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 20:12:13 -05:00

3.9 KiB

OAuth / OIDC SSO Setup (Authentik)

Fabled Assistant 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: Fabled Assistant (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 Fabled Assistant

Add the following environment variables to the app service in docker-compose.yml:

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 in Authentik
      BASE_URL: "https://your-fabled-domain"

Docker Secrets alternative: Instead of OIDC_CLIENT_SECRET, you can use OIDC_CLIENT_SECRET_FILE pointing to a Docker secret file.

Rebuild and restart:

docker compose up --build -d

3. Verify

  1. Open /api/auth/status — it should return:
    { "oauth_enabled": true, "local_auth_enabled": true, ... }
    
  2. Go to the login page — you should see a "Login with Authentik" button.
  3. Click it → you are redirected to Authentik → authenticate → redirected back to Fabled → 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, Fabled 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 address, the OAuth identity is linked to it automatically. The user retains all their notes and tasks.
  3. New user — a fresh account is created. The username defaults to the preferred_username claim from the provider; if taken, _2, _3, etc. is appended.

5. Disable local login (optional)

Once everyone is using SSO you can hide the username/password form:

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 Fabled container (server-to-server call).