# OAuth / OIDC SSO Setup

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`:

```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, 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, 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 Fabled container (server-to-server call at login time).
