Google SSO setup (admin sign-in)
Admin sign-in uses Google, restricted to the nightingalepm.com Google Workspace. This is the
one part of Step 2 that must be done in the Google and Supabase dashboards. The app already
enforces the domain server-side in the OAuth callback, so even if the Google hint is bypassed,
non-@nightingalepm.com accounts are rejected.
Project ref: dksjqoknmwpxfqkiygkb · Supabase URL: https://dksjqoknmwpxfqkiygkb.supabase.co
1. Google Cloud — OAuth client
- Go to https://console.cloud.google.com/ and select (or create) a project for Nightingale.
- APIs & Services → OAuth consent screen:
- User type: Internal (this alone restricts sign-in to your Workspace; keep it Internal).
- App name: "Nightingale Process Management", support email: your Workspace email.
- Save.
- APIs & Services → Credentials → Create credentials → OAuth client ID:
- Application type: Web application.
- Name: "Nightingale Web".
- Authorized redirect URIs — add the full callback path, exactly:
https://dksjqoknmwpxfqkiygkb.supabase.co/auth/v1/callback- ⚠️ Must include
/auth/v1/callback. The bare domain (https://dksjqoknmwpxfqkiygkb.supabase.co) does not match — Google compares redirect URIs as exact strings and will reject withError 400: redirect_uri_mismatch.
- Create, then copy the Client ID and Client secret.
Note — two OAuth clients in this project. The Nightingale GCP project also has a separate client for Gmail inbox reading ("NPM OAuth client", ID
…em7k…). SSO uses a different client ("NPM Admin SSO", ID…6pss…). Make sure the redirect URI above is on the Admin SSO client, and that Supabase (step 2) gets the Admin SSO Client ID/secret — not the Gmail one.
2. Supabase — enable the Google provider
- Dashboard → your project → Authentication → Providers → Google.
- Toggle Enable, paste the Client ID and Client secret from step 1, and save.
3. Supabase — URL configuration
Dashboard → Authentication → URL Configuration:
- Site URL:
http://localhost:3000(for local dev; set to the production URL when deployed). - Redirect URLs — add:
http://localhost:3000/api/auth/callback- (later)
https://<your-production-domain>/api/auth/callback
4. Local env
.env.local already has NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and
ADMIN_SSO_ALLOWED_DOMAIN=nightingalepm.com. No Google client secret is needed in the app — it
lives in Supabase (Supabase performs the token exchange). Optionally set GOOGLE_SSO_CLIENT_ID
/ GOOGLE_SSO_CLIENT_SECRET in .env.local for reference only.
5. Try it
pnpm dev, open http://localhost:3000/login.- Click Sign in with Google, choose your
@nightingalepm.comaccount. - You should land on
/admin. The first admin to sign in becomes the owner; later admins are staff. - A non-
@nightingalepm.comaccount is bounced back to/loginwith a clear error.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
Error 400: redirect_uri_mismatch (no account picker appears) | The redirect URI registered on the Admin SSO client is wrong or incomplete — most commonly the bare domain instead of the full …supabase.co/auth/v1/callback. Google validates the URI before showing the picker, so this blocks everything. Fix the URI, save, wait ~1–5 min to propagate. |
| Same error, but the URI looks correct | You edited the wrong client (Gmail …em7k… instead of Admin SSO …6pss…), or Supabase has the wrong Client ID. Confirm both point at …6pss…. Also check for a trailing slash/space or http://. |
Dev server came up on :3001, login redirect rejected | Something else holds :3000. The redirect allow-list (step 3) is set for :3000, so free that port (lsof -i :3000) and restart, or add the :3001 URLs to Supabase. |
| "No option to sign in with my account" / account is hidden | Expected. The hd: nightingalepm.com hint hides non-Workspace accounts. Sign in with a @nightingalepm.com account (use Use another account if needed). A personal/other-domain Google account cannot sign in by design. |
How it works (for reference)
- Login button →
supabase.auth.signInWithOAuth({ provider: 'google', hd: 'nightingalepm.com' }). - Google → Supabase callback (
/auth/v1/callback) → app callback/api/auth/callback. - The callback verifies the email domain (src/server/auth/domain.ts), creates the profile if needed (profile.ts), and records the session (session.ts).
- src/proxy.ts gates
/admin, refreshes the session, and enforces force-logout (a revokedsessionsrow) and the per-role absolute timeout.