# SSO (Single Sign-On)

> Configure OAuth-based single sign-on with Google, Microsoft, GitHub, Facebook, or any OIDC provider

Source: https://docs.soosh.io/features/sso/

Soosh supports SSO via OAuth 2.0 / OIDC. Providers are **platform-wide**: a super admin sets them up once, and users of every organization sign in through them and land in their own organization. SSO only signs in people an organization has already added; it never creates accounts.

## Supported Providers

  **Google**

    Google Workspace and personal accounts.
  
  **Microsoft**

    Azure AD / Entra ID via the `common` endpoint. Only for accounts that are already linked; see [Linking accounts](#linking-accounts).
  
  **GitHub**

    GitHub.com accounts. Pulls primary verified email if private.
  
  **Facebook**

    Facebook accounts with email permission.
  
  **Custom (OIDC)**

    Any OIDC-compatible provider — Okta, Auth0, Keycloak, Authentik, etc. Requires you to supply the auth/token/userinfo URLs.
  

## How It Works

1. **User clicks "Sign in with \<provider\>"** on the login page. The list of buttons is driven by which platform providers are enabled via `GET /api/auth/sso/providers` (public endpoint, no secrets exposed).

2. **Frontend hits `/api/auth/sso/{provider}/init`.** The server generates a random state nonce, stores `{provider, nonce, expires_at}` in Redis with a 5-minute TTL, and returns the provider's authorization URL.

3. **User authenticates with the provider** and grants consent.

4. **Provider redirects to `/api/auth/sso/{provider}/callback?code=...&state=...`.** The server:
   - Validates and **immediately deletes** the state from Redis (one-shot, prevents replay).
   - Exchanges the authorization code for an access token.
   - Calls the provider's userinfo endpoint to get `id`, `email`, `name`, and whether the email is verified.
   - Checks the email domain against `allowed_domains` if configured.
   - Looks up the user by email. Unknown emails, disabled users, super admins, and orgs with the `sso` feature turned off are rejected.
   - Checks or creates the link between the user and this provider identity (see [Linking accounts](#linking-accounts)).
   - Logs the user into **their own** default organization.
   - Sets httpOnly JWT cookies and redirects to `/auth/sso/callback` in the SPA.

> **Note:** Tokens are stored in httpOnly cookies, never in the URL or localStorage — same as password login.

## Configuration

Settings live under **Platform → Sign-in (SSO)**, visible to super admins only. All providers share the same core fields; **Custom** adds three more.

| Field | Required | Notes |
|---|---|---|
| `client_id` | yes | OAuth client ID from the provider's developer console. |
| `client_secret` | yes | Stored encrypted at rest using `app.encryption_key`. Cannot be retrieved after save — only re-set. |
| `is_enabled` | — | Master switch. Disabled providers don't appear on the login page. |
| `allowed_domains` | — | Comma-separated email domains, e.g. `acme.com,acme.co.in`. Empty = any domain accepted. |
| `auth_url` | custom only | OAuth 2.0 authorization endpoint. |
| `token_url` | custom only | OAuth 2.0 token endpoint. |
| `user_info_url` | custom only | OIDC userinfo endpoint. Must return `sub` (or `id`), `email`, and `name` (or `preferred_username`). |

### Provider redirect URI

When you create the OAuth app at the provider, set the **redirect URI** (a.k.a. callback URL) to:

```
https://<your-host>/api/auth/sso/{provider}/callback
```

Replace `{provider}` with `google`, `microsoft`, `github`, `facebook`, or `custom`. If you serve Soosh at a sub-path (`server.base_path = "/soosh"`), include it: `https://example.com/soosh/api/auth/sso/google/callback`.

> **Careful:** The provider must use HTTPS for redirect URIs in production. HTTP is only allowed when `app.environment = "development"`.

## Linking accounts

Organization admins add users by email under **Settings → Users**, as usual. The first time that person signs in with a provider, Soosh links the provider identity to the user, **but only if the provider verified the email**:

| Provider | Verified email comes from |
|---|---|
| Google | `verified_email` in userinfo |
| GitHub | the verified-emails API (`/user/emails`) |
| Facebook | Facebook only returns confirmed emails |
| Custom (OIDC) | `email_verified: true` in userinfo |
| Microsoft | never: Graph's `mail` can be set by any tenant admin ("nOAuth"), so Microsoft cannot link new accounts |

After that, the link is pinned. The same provider identity signs in without further checks. A *different* identity, from the same provider or another one, cannot take the account over while the linked provider is enabled, even with a verified email. Each user has one link; if its provider is removed or disabled, the user can link another with a verified email.

Super admins can't sign in with SSO; they keep their own sign-in method.

## Organizations

- One set of provider apps serves every organization, so you register a single OAuth app per provider.
- A user who belongs to several organizations lands in their default one and can switch from the UI.
- To turn SSO off for one organization, disable its `sso` feature under **Platform → Organizations → (org) → Features**. Its users are then rejected at the callback; other organizations are unaffected.

## Security

| Concern | Mitigation |
|---|---|
| **Client secret leak** | Stored AES-256 encrypted using `app.encryption_key`. Set this in production — without it, secrets are stored in plaintext. |
| **CSRF / replay on callback** | State nonce stored in Redis (5-min TTL) and deleted on first use. Replays fail. |
| **Brute force on callback** | `rate_limit.sso_max_attempts` (default 10/min/IP) caps both `init` and `callback`. Set `rate_limit.trust_proxy = true` if behind a reverse proxy. |
| **Account takeover by email** | New links need a provider-verified email; existing links are pinned to one provider identity; super admins are excluded. |
| **Disabled accounts** | Users with `is_active = false` are rejected at the end of the SSO flow. |

## Custom OIDC Providers

For Okta, Auth0, Keycloak, Authentik, etc., choose **Custom** and supply:

| Field | Example (Keycloak) |
|---|---|
| `auth_url` | `https://kc.example.com/realms/main/protocol/openid-connect/auth` |
| `token_url` | `https://kc.example.com/realms/main/protocol/openid-connect/token` |
| `user_info_url` | `https://kc.example.com/realms/main/protocol/openid-connect/userinfo` |

Soosh requests the `openid email profile` scopes and expects the userinfo response to contain at least one of: `sub` or `id`, plus `email`, plus `name` or `preferred_username`. Most OIDC providers return this out of the box.

## Provider-specific notes

### Google

Configure the OAuth 2.0 client at [console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials). Set the **Authorized redirect URI** to your Soosh callback. Standard Google sign-in flow — no extra setup.

### Microsoft

Uses Azure AD's `common` endpoint. Register the app at [entra.microsoft.com](https://entra.microsoft.com) → App registrations. Email is read from `mail`, falling back to `userPrincipalName`. Because neither is verified, Microsoft only signs in users linked to a Microsoft identity earlier.

### GitHub

Register an OAuth App at [github.com/settings/developers](https://github.com/settings/developers). Soosh requests the `user:email` scope and always uses the user's primary verified email from `/user/emails`.

### Facebook

Register the app at [developers.facebook.com/apps](https://developers.facebook.com/apps). Add the **Facebook Login** product. Note that Facebook may not return an email if the user has no verified email on file — the SSO will fail with "email not provided" in that case.

## Common Issues

| Symptom | Likely cause |
|---|---|
| `User not found. Ask your organization admin to add you.` | No user has this email. An organization admin adds them under Settings → Users. |
| `This account is not linked to this identity provider…` | The provider didn't verify the email (always the case for Microsoft), the account is linked to a different identity, or it's a super admin. |
| `SSO is disabled for your organization` | The user's organization has the `sso` feature turned off. |
| `Email domain not allowed` | The user's email domain isn't in `allowed_domains`. Either add their domain or remove the restriction. |
| `Invalid or expired state` | The state nonce expired (>5 min between init and callback) or was already used. Have the user start the login again. |
| `Failed to authenticate with provider` | The OAuth code exchange failed — usually a redirect URI mismatch or an invalid client secret. Re-check both at the provider. |
| `email not provided by SSO provider` | Provider returned no email. For GitHub, ensure the user has a verified email; for Facebook, ensure email permission was granted. |
| `Account is disabled` | User exists but `is_active = false`. Re-enable under Settings → Members. |
| Provider button missing on login page | The provider isn't enabled under Platform → Sign-in (SSO). |
