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)
- Google Cloud Console → APIs & Services → Credentials → Create OAuth client ID → Web application.
- 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). - Copy the client ID and secret.
- In Django admin (
/admin/) create/update:- A
Social application(allauth'sSocialApp) with providerGoogle, the client ID and secret from step 3, and this environment's site. - An
IdpConnectionpointing at thatSocialApp, withis_active=True.
- A
- (For directory sync only) On the same
IdpConnection, set the service-account JSON anddelegated_admin_emailfor a Workspace service account with Domain-Wide Delegation and the Admin SDK Directory API scopes (seeDIRECTORY_SCOPESinservices_google.py). - 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
- Complete setup steps 1–4 above for
http://localhost:8000. - Start the stack (
./dev.sh) and open the frontend. - Confirm the "Sign in with Google" button appears — this means
GET /api/auth/sso-providersreturned your active connection. If it doesn't appear, checkIdpConnection.is_activein admin. - Click it, complete the Google consent screen with an account you control.
- You should land back on the frontend, logged in. Check:
GET /api/auth/mereturns your user.- Django admin →
identity→External identitieshas a new row linking your user to theIdpConnection.
- To test a second/returning login, log out and sign in again — the same
ExternalIdentityrow should be reused (update_or_createonidp_connection+external_id), not duplicated.
How it works
See SSO internals for the login and directory sync flows.