Google login is the default social auth for a reason: nearly every user on the planet has a Google account, and "Sign in with Google" converts better than any registration form you'll ever build. On Supabase Cloud, enabling it is a dashboard toggle. On self-hosted Supabase, that toggle doesn't exist — the Studio UI renders the switch, but flipping it does nothing, a gotcha that has kept GitHub Discussion #4885 active for years. The Auth service (GoTrue) reads OAuth configuration exclusively from environment variables.
This guide covers the full path: creating credentials in Google Cloud Console, wiring the GoTrue environment variables, adding Google One Tap, and fixing the errors that account for most of the support threads. If you're setting up several providers at once, our overview of setting up OAuth providers for self-hosted Supabase covers the shared plumbing; this post goes deep on Google specifically.
Step 1: Create OAuth Credentials in Google Cloud Console
Head to the Google Cloud Console and create a project (or reuse an existing one — the OAuth client lives inside a project, but nothing else about the project matters for auth).
Configure the consent screen first
Under APIs & Services → OAuth consent screen (Google now calls this "Google Auth Platform"):
- Choose External as the user type unless every user is inside your Google Workspace org.
- Fill in the app name, support email, and developer contact.
- Under Authorized domains, add the domain your Supabase instance runs on (e.g.
yourdomain.com). - Scopes: the defaults —
openid,email,profile— are all GoTrue needs. Don't request more; extra scopes trigger Google's verification review.
You can stay in Testing mode while you build (up to 100 listed test users), but remember to Publish before launch — testing-mode refresh behavior expires sessions after seven days and confuses everyone.
One thing the consent screen will show your users: the raw domain of your API endpoint. If that's an ugly subdomain or a bare IP, users see it on the Google popup. We've written about fixing Google OAuth branding without a custom domain — worth reading before you launch, because the fix affects which redirect URI you register below.
Create the client ID
Under APIs & Services → Credentials → Create Credentials → OAuth client ID:
- Application type: Web application
- Authorized JavaScript origins: your app's URL (e.g.
https://app.yourdomain.com) — required for One Tap later - Authorized redirect URIs: your Supabase Auth callback:
https://api.yourdomain.com/auth/v1/callback
This is the single most common failure point. The redirect URI is your API gateway (Kong) URL plus /auth/v1/callback — not your frontend app's URL, not the Studio URL, and not localhost in production. It must match what GoTrue reports character-for-character: same scheme, same host, no trailing slash.
Copy the Client ID and Client Secret.
Step 2: Configure GoTrue Environment Variables
Google follows the standard GOTRUE_EXTERNAL_{PROVIDER}_* convention. Add these to the .env file of your Docker Compose stack:
GOTRUE_EXTERNAL_GOOGLE_ENABLED=true GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=1234567890-abc123.apps.googleusercontent.com GOTRUE_EXTERNAL_GOOGLE_SECRET=GOCSPX-your-client-secret GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=https://api.yourdomain.com/auth/v1/callback
Then make sure the auth service in docker-compose.yml actually passes them through — the stock Supabase compose file does not forward arbitrary variables:
auth:
environment:
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOTRUE_EXTERNAL_GOOGLE_ENABLED}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOTRUE_EXTERNAL_GOOGLE_SECRET}
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI}
Recreate the container — a restart alone won't reload environment variables:
docker compose up -d auth
Verify GoTrue picked it up:
curl -s https://api.yourdomain.com/auth/v1/settings \ -H "apikey: $ANON_KEY" | jq '.external.google'
If that returns true, the provider is live. If it returns false, the variables aren't reaching the container — check docker compose exec auth env | grep GOOGLE.
Don't skip SITE_URL
Two more variables decide where users land after Google hands them back:
GOTRUE_SITE_URL=https://app.yourdomain.com GOTRUE_URI_ALLOW_LIST=https://app.yourdomain.com/**,http://localhost:3000/**
Misconfigure these and login "works" but strands users on the wrong domain or silently drops their session. The full behavior — including wildcard rules and the localhost trap — is covered in our guide to redirect URLs and Site URL configuration.
Step 3: Trigger the Flow from Your App
Client-side, nothing differs from Supabase Cloud:
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://app.yourdomain.com/auth/callback',
},
});
The redirectTo value must fall inside GOTRUE_URI_ALLOW_LIST. If you need a refresh token for calling Google APIs on the user's behalf (Calendar, Drive), add:
options: {
queryParams: {
access_type: 'offline',
prompt: 'consent',
},
},
Without access_type: 'offline', Google never issues a refresh token — a classic "worked in testing, broke for real users" bug.
Step 4 (Optional): Google One Tap and ID Token Sign-In
The redirect flow works everywhere, but Google One Tap — the floating credential popup — measurably lifts sign-in rates. It uses a different mechanism: Google's client library returns an ID token directly, which you exchange with GoTrue:
const { data, error } = await supabase.auth.signInWithIdToken({
provider: 'google',
token: credentialResponse.credential, // from Google Identity Services
});
Two self-hosted-specific requirements:
- Your app's origin must be listed under Authorized JavaScript origins on the OAuth client.
- If you use One Tap through certain libraries (or Chrome's FedCM), the nonce embedded in the token may not survive the round trip. GoTrue exposes an escape hatch:
GOTRUE_EXTERNAL_GOOGLE_SKIP_NONCE_CHECK=true
Only enable this if you hit nonce mismatch errors — it weakens replay protection, so treat it as a workaround, not a default. The same flag matters for native iOS Google Sign-In, where the SDK doesn't expose the nonce at all. If you're building mobile, pair this with our guide to deep linking for self-hosted Supabase mobile auth.
Common Errors and Fixes
redirect_uri_mismatch (from Google). The URI GoTrue sent doesn't exactly match a registered one. Check the error detail — Google shows you the URI it received. Usual causes: http vs https behind a reverse proxy (set X-Forwarded-Proto correctly in Nginx/Traefik), a trailing slash, or registering the frontend URL instead of the Kong callback.
Unsupported provider: provider is not enabled (from GoTrue). The ENABLED variable isn't reaching the container. Nine times out of ten the variable is in .env but missing from the environment: block in docker-compose.yml.
Consent screen shows an IP address or wrong domain. You're serving auth from a bare IP or a default subdomain. Bind a proper domain to your instance — see our custom domains setup guide.
Login loops back to the sign-in page. Almost always GOTRUE_SITE_URL pointing at the API domain instead of the app domain, or the app's callback route failing to exchange the code for a session.
Works locally, fails in production. Testing-mode consent screen + non-test-user account. Publish the app in Google Cloud Console.
The Configuration-File Problem
Notice what this guide actually consisted of: editing environment files, mirroring variables into a compose file, recreating containers, and curl-ing a settings endpoint to see whether it stuck. Multiply that by every provider (GitHub, Discord, Apple, Azure), every project, and every secret rotation, and OAuth config becomes a recurring operational chore — with a typo in a .env file taking down login for every user.
This is one of the gaps Supascale was built to close. It gives self-hosted Supabase the provider UI that Studio only pretends to have: enable Google, paste the client ID and secret into a form, and Supascale writes the GoTrue configuration and restarts the right service. It shows you the exact callback URL to register with Google, per project. The license is one-time from $99 and covers unlimited projects, so the tenth provider setup costs the same as the first: about two minutes.
Conclusion
Google OAuth on self-hosted Supabase comes down to four things: an OAuth client in Google Cloud Console with the Kong callback URL registered, four GOTRUE_EXTERNAL_GOOGLE_* variables that actually reach the auth container, a correct SITE_URL and allow list, and a published consent screen before launch. The flow itself is standard — every failure mode is configuration, which means every failure mode is checkable: curl the settings endpoint, read the exact URI in Google's error message, and grep the container's environment before assuming the code is wrong.
