Skip to main content

Single sign-on

What this covers​

Users can sign in with their Google Workspace account instead of (or in addition to) a username/password. Once an admin connects a Google Workspace IdP, the backend can also pull groups and users from that Workspace on a schedule or on demand ("directory sync"), which is used to keep ExternalGroup/ExternalIdentity records up to date. A Team can be linked to one synced group; that Team's members, and the permissions that hang off the Team, then follow the group (adrs/0009.md). The group itself grants nothing.

This was ported from perspective (respective docs: https://github.com/dyvenia/perspective/tree/main/docs/internal/identity). Some things that existed there were deliberately not brought over:

  • Microsoft Entra ID support (Google only, for now)
  • MCP OAuth 2.1 authorization server (django-oauth-toolkit)
  • API tokens for programmatic access (ApiToken)

If you're wondering why a Microsoft-shaped field or comment shows up somewhere, that's why.

One-time setup (per environment)​

  1. Google Cloud Console → APIs & Services → Credentials → Create OAuth client ID → Web application.
  2. Add the exact redirect URI for this environment: <BACKEND_BASE_URL>/accounts/google/login/callback/ — note the trailing slash, and match scheme/host/port exactly (http://localhost:8000/... for local dev).
  3. Copy the client ID and secret.
  4. In Django admin (/admin/) create/update:
    • A Social application (allauth's SocialApp) with provider Google, the client ID and secret from step 3, and this environment's site.
    • An IdpConnection pointing at that SocialApp, with is_active=True.
  5. (For directory sync only) On the same IdpConnection, set the service-account JSON and delegated_admin_email for a Workspace service account with Domain-Wide Delegation and the Admin SDK Directory API scopes (see DIRECTORY_SCOPES in services_google.py).
  6. Confirm the OAuth consent screen isn't stuck in "Testing" mode with your test account missing from the allow-list, or real users will be blocked by Google before reaching the app at all.

Testing SSO login locally​

  1. Complete setup steps 1–4 above for http://localhost:8000.
  2. Start the stack (./dev.sh) and open the frontend.
  3. Confirm the "Sign in with Google" button appears — this means GET /api/auth/sso-providers returned your active connection. If it doesn't appear, check IdpConnection.is_active in admin.
  4. Click it, complete the Google consent screen with an account you control.
  5. You should land back on the frontend, logged in. Check:
    • GET /api/auth/me returns your user.
    • Django admin → identity → External identities has a new row linking your user to the IdpConnection.
  6. To test a second/returning login, log out and sign in again — the same ExternalIdentity row should be reused (update_or_create on idp_connection + external_id), not duplicated.

How it works​

See SSO internals for the login and directory sync flows.