Skip to main content
OAuth lives at:
GET /v1/auth/oauth/google
GET /v1/auth/oauth/github
These are not JSON endpoints — they are HTTP redirects intended for the browser. Hit them by navigating, not via fetch.

Initiating

The dashboard’s login page has buttons that simply link to these paths:
<a href="/v1/auth/oauth/google">Sign in with Google</a>
<a href="/v1/auth/oauth/github">Sign in with GitHub</a>
When the user clicks:
  1. The server generates a CSRF state value + PKCE code-verifier
  2. Stores both in a short-lived cookie
  3. 302s to the provider’s consent screen with state and code_challenge

Callback

After the user approves at Google / GitHub:
GET /v1/auth/oauth/google/callback?code=…&state=…
GET /v1/auth/oauth/github/callback?code=…&state=…
The server:
  1. Validates state matches the cookie value (rejects with 400 otherwise)
  2. Exchanges code for an ID token (Google) or access token (GitHub)
  3. Extracts the verified email
  4. Upserts the user by email
  5. Creates a session, sets the sid cookie
  6. 302s to /app
If the user already had an account on the same email (from email OTP, or the other OAuth provider), it’s the same account. We merge on email.

Error redirects

URL paramCause
/login?error=oauth_unavailableThe provider isn’t configured on the server (missing client ID/secret)
/login?error=oauth_no_emailThe provider didn’t return a verified email
/login?error=oauth_failedToken exchange failed; user cancelled; state mismatch
The dashboard’s login page reads ?error= and displays a human-readable message in red.

What scopes we request

ProviderScopeWhat we get
Googleopenid email profileEmail, sub (Google user ID)
GitHubread:user user:emailPrimary verified email, GitHub user ID
We discard the sub / GitHub ID after the initial lookup — we only need to confirm the verified email.

Disabling a provider

If GOOGLE_CLIENT_ID or GITHUB_CLIENT_ID is unset in the server’s environment, the corresponding endpoint returns:
302 /login?error=oauth_unavailable
The dashboard’s social buttons stay visible; they just bounce back with an error message. To hide them entirely, the deployment would need a build- time env var (VITE_OAUTH_GOOGLE_ENABLED=0).