apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx
This guide covers the server-side configuration required to enable social login providers on a self-hosted Supabase instance running with Docker Compose. This applies to all OAuth and OIDC-based providers, including third-party identity providers like Keycloak.
You need:
API_EXTERNAL_URL set to the publicly reachable URL of your Supabase instance (e.g., https://<your-domain>).HTTPS is strongly recommended in production. Most OAuth providers reject http:// callback URLs (except localhost).
Your OAuth callback URL is built from API_EXTERNAL_URL. For example, if API_EXTERNAL_URL is https://<your-domain>, the callback URL will become:
https://<your-domain>/auth/v1/callback
You will have to register this URL with each OAuth provider.
When a user signs in with an OAuth provider, the following flow occurs:
supabase.auth.signInWithOAuth() and the browser redirects to the Auth service/auth/v1/authorize)https://<your-domain>/auth/v1/callbackSITE_URL or an allowed redirect URLThe Auth service (GoTrue) uses the prefix GOTRUE_EXTERNAL_ followed by a provider name for all OAuth configuration. For example, when using Google:
GOTRUE_EXTERNAL_GOOGLE_ENABLEDGOTRUE_EXTERNAL_GOOGLE_CLIENT_IDGOTRUE_EXTERNAL_GOOGLE_SECRETGOTRUE_EXTERNAL_GOOGLE_REDIRECT_URIThe default .env.example and docker-compose.yml include commented-out placeholders for Google, GitHub, and Azure.
https://<your-domain>/auth/v1/callback.env file.Uncomment the lines for your provider in .env and add your client ID and secret, e.g., for Google:
GOOGLE_ENABLED=true
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_SECRET=your-client-secret
Uncomment the corresponding GOTRUE_EXTERNAL_ lines in the auth service's environment:
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET}
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
For providers not pre-configured in the files (see the full provider list below), add the lines manually following the same pattern: variables in .env, passthrough with GOTRUE_EXTERNAL_PROVIDER_ in docker-compose.yml.
docker compose up -d --force-recreate --no-deps auth
Check that the provider is enabled:
curl -H 'apikey: your-anon-key' https://<your-domain>/auth/v1/settings
The response should include your provider under external:
{
"external": {
"google": true
}
}
<Tabs scrollable size="small" type="underlined" defaultActiveId="google"
<TabPanel id="google" label="Google">
Google Cloud Console setup:
https://<your-domain>/auth/v1/callback.env:
GOOGLE_ENABLED=true
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_SECRET=your-google-client-secret
docker-compose.yml:
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET}
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
GitHub setup:
https://<your-domain>https://<your-domain>/auth/v1/callback.env:
GITHUB_ENABLED=true
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_SECRET=your-github-client-secret
docker-compose.yml:
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_GITHUB_ENABLED: ${GITHUB_ENABLED}
GOTRUE_EXTERNAL_GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GOTRUE_EXTERNAL_GITHUB_SECRET: ${GITHUB_SECRET}
GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
Azure Portal setup:
https://<your-domain>/auth/v1/callback.env:
AZURE_ENABLED=true
AZURE_CLIENT_ID=your-azure-application-client-id
AZURE_SECRET=your-azure-client-secret
## Optional: restrict to a specific tenant (defaults to 'common')
# AZURE_URL=https://login.microsoftonline.com/your-tenant-id
docker-compose.yml:
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_AZURE_ENABLED: ${AZURE_ENABLED}
GOTRUE_EXTERNAL_AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
GOTRUE_EXTERNAL_AZURE_SECRET: ${AZURE_SECRET}
GOTRUE_EXTERNAL_AZURE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
## Optional: uncomment for tenant-specific Azure login
# GOTRUE_EXTERNAL_AZURE_URL: ${AZURE_URL}
Apple Developer setup:
.env:
APPLE_ENABLED=true
APPLE_CLIENT_ID=com.example.your-services-id
APPLE_SECRET=your-generated-jwt-client-secret
docker-compose.yml:
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_APPLE_ENABLED: ${APPLE_ENABLED}
GOTRUE_EXTERNAL_APPLE_CLIENT_ID: ${APPLE_CLIENT_ID}
GOTRUE_EXTERNAL_APPLE_SECRET: ${APPLE_SECRET}
GOTRUE_EXTERNAL_APPLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
Apple uses response_mode=form_post for its OAuth flow. The Auth service handles this automatically - no additional configuration is needed.
Keycloak setup:
supabase)https://<your-domain>/auth/v1/callback.env variables:
KEYCLOAK_ENABLED=true
KEYCLOAK_CLIENT_ID=supabase
KEYCLOAK_SECRET=your-keycloak-client-secret
## Required: your Keycloak realm URL
KEYCLOAK_URL=https://keycloak.example.com/realms/myrealm
docker-compose.yml passthrough:
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_KEYCLOAK_ENABLED: ${KEYCLOAK_ENABLED}
GOTRUE_EXTERNAL_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID}
GOTRUE_EXTERNAL_KEYCLOAK_SECRET: ${KEYCLOAK_SECRET}
GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
GOTRUE_EXTERNAL_KEYCLOAK_URL: ${KEYCLOAK_URL}
KEYCLOAK_URL is required. It must be the full realm URL (e.g., https://keycloak.example.com/realms/myrealm). The Auth service uses this to discover the OIDC endpoints (.well-known/openid-configuration). Without it, Keycloak login will not work.
Supabase Auth supports the following OAuth providers:
| Provider | Env prefix | Additional variables | Docs |
|---|---|---|---|
| Apple | APPLE_ | - | Login with Apple |
| Azure (Microsoft) | AZURE_ | URL (tenant URL) | Login with Azure |
| Bitbucket | BITBUCKET_ | - | Login with Bitbucket |
| Discord | DISCORD_ | - | Login with Discord |
FACEBOOK_ | - | Login with Facebook | |
| Figma | FIGMA_ | - | Login with Figma |
| GitHub | GITHUB_ | URL (for GitHub Enterprise) | Login with GitHub |
| GitLab | GITLAB_ | URL (for self-hosted GitLab) | Login with GitLab |
GOOGLE_ | - | Login with Google | |
| Kakao | KAKAO_ | - | Login with Kakao |
| Keycloak (OIDC) | KEYCLOAK_ | URL (realm URL, required) | Login with Keycloak |
| LinkedIn (OIDC) | LINKEDIN_OIDC_ | - | Login with LinkedIn |
| Notion | NOTION_ | - | Login with Notion |
| Slack (OIDC) | SLACK_OIDC_ | - | Login with Slack |
| Snapchat | SNAPCHAT_ | - | - |
| Spotify | SPOTIFY_ | - | Login with Spotify |
| Twitch | TWITCH_ | - | Login with Twitch |
TWITTER_ | - | Login with Twitter | |
| WorkOS | WORKOS_ | - | Login with WorkOS |
| Zoom | ZOOM_ | - | Login with Zoom |
For each provider, you need at minimum ENABLED, CLIENT_ID, SECRET, and REDIRECT_URI in .env and docker-compose.yml.
LinkedIn (OIDC) and Slack (OIDC) use multi-word env prefixes. The full Docker Compose variables are GOTRUE_EXTERNAL_LINKEDIN_OIDC_CLIENT_ID and GOTRUE_EXTERNAL_SLACK_OIDC_CLIENT_ID respectively - not LINKEDIN_CLIENT_ID or SLACK_CLIENT_ID.
You can test OAuth with the following minimal HTML page:
index.htmlpython -m http.server 3000 in the same directorySITE_URL is set to http://localhost:3000 in your self-hosted Supabase .env configurationhttp://localhost:3000<!doctype html>
<html>
<body>
<h1>Supabase OAuth Test</h1>
<button id="loginBtn">Sign in with Google</button>
<pre id="result"></pre>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const SUPABASE_URL = 'https://<your-domain>'
const SUPABASE_ANON_KEY = 'your-anon-key'
const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
const button = document.getElementById('loginBtn')
button.addEventListener('click', async () => {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
})
if (error) {
document.getElementById('result').textContent = JSON.stringify(error, null, 2)
}
})
supabase.auth.getSession().then(({ data }) => {
if (data.session) {
document.getElementById('result').textContent =
'Logged in as: ' + data.session.user.email
}
})
})
</script>
</body>
</html>
For detailed client-side integration, see Social Login.
GOTRUE_EXTERNAL_*_ENABLED is set to true in docker-compose.yml.env variable is not empty, e.g., check with docker compose exec auth env | grep GOOGLEConfiguration variables from .env are not automatically available inside the container unless there's a matching passthrough definition in docker-compose.yml. Check, e.g., for:
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
Run docker compose exec auth env | grep GOTRUE_EXTERNAL to verify the variables are reaching the container.
After a successful OAuth login, the Auth service redirects to SITE_URL or a URL from ADDITIONAL_REDIRECT_URLS. Ensure:
SITE_URL in .env is set to your application's URLADDITIONAL_REDIRECT_URLS (comma-separated)When using Google Sign In on mobile with ID tokens, nonce verification may fail because mobile SDKs don't always support the nonce flow that the Auth service expects.
<Admonition type="caution">GOTRUE_EXTERNAL_SKIP_NONCE_CHECK disables nonce validation on ID tokens, which weakens replay-attack protection. Treat it as a short-lived troubleshooting workaround, not a permanent fix:
To enable it, uncomment the following line in docker-compose.yml:
GOTRUE_EXTERNAL_SKIP_NONCE_CHECK: true
Check the auth container logs:
docker compose logs auth
Common causes:
CLIENT_ID or SECRET is empty)API_EXTERNAL_URL (must be a valid URL with protocol)All OAuth-related environment variables for the auth service in docker-compose.yml:
| Variable | Description | Required |
|---|---|---|
GOTRUE_EXTERNAL_*_ENABLED | Enable the provider (true/false) | Yes |
GOTRUE_EXTERNAL_*_CLIENT_ID | OAuth client ID from the provider | Yes |
GOTRUE_EXTERNAL_*_SECRET | OAuth client secret from the provider | Yes |
GOTRUE_EXTERNAL_*_REDIRECT_URI | Callback URL: ${API_EXTERNAL_URL}/auth/v1/callback | Yes |
GOTRUE_SITE_URL | Default redirect URL after authentication (set via SITE_URL in .env) | Yes |
example.env)