Files
supabase/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx
T
Miranda Limonczenko 7ce4ee53ae chore(docs) Retire supa-mdx-lint (#50602)
Closes
[DOCS-1289](https://linear.app/supabase/issue/DOCS-1289/get-the-linter-to-fix-what-it-flags-or-retirereplace-the-linter)

Stacked on #50600, which points contributors at the authoring skills.
Merge that one first.

## Problem

Contributors experienced friction with the linter. They felt nickle and
dimed for tiny nits and felt detracted from the work itself. PRs would
become noisy with tiny one-word suggestions.

Additionally, our homegrown linter is not very intelligent, causing
frequent overrides.

## Solution

This removes the linter entirely in favor of directing contributors to
use SKILLS instead.

The removal entails...

- **CI.** Delete the three `docs_lint` workflows: the PR check, the
external-PR comment companion, and the nightly `--fix` bot. Drop the
stale `zizmor.yml` ignore entry for the deleted workflow.
- **Tooling.** Delete `supa-mdx-lint.config.toml` and the 14 rule files.
Drop the `lint:mdx` script and the `@supabase/supa-mdx-lint` dependency
from docs, learn, and ui-library, and regenerate the lockfile.
- **Content.** Remove the 181 directives. A separate commit carries
Prettier's reformatting of the tables and blank lines those comments had
suppressed, so the deletion commit stays readable. No prose changes.
- **Style guide.** The word list states each rule directly instead of
describing what the linter flagged. Every term survives, including the
phrase groups that mirrored `Rule004ExcludeWords`.
- **Skills.** `write-the-docs`, `edit-the-docs`, and `review-the-docs`
drop `pnpm lint:mdx` from their self-review commands and check the word
list directly. `ask-the-docs`'s CI reference drops both workflows.

## Manual testing

1. Run `git grep -i supa-mdx-lint -- . ':!pnpm-lock.yaml'`. No matches.
2. Run `pnpm install --frozen-lockfile --lockfile-only`. It passes, so
the lockfile matches the three trimmed manifests.
3. Run `git diff master...HEAD --name-only --diff-filter=ACMR | grep -E
'\.(md|mdx)$' | xargs npx prettier --config prettier.config.mjs
--check`. All changed markdown passes.
4. Open the [reformatted filter
table](https://docs-git-docs-retire-mdx-linter-supabase.vercel.app/docs/guides/observability/logs#filter-events)
on the preview and compare it with
[production](https://supabase.com/docs/guides/observability/logs#filter-events).
The table renders the same.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Documentation guidance now uses manual prose and terminology review
with the shared word list.
* Clarified storage configuration and common Realtime channel mistakes.
* Improved table formatting, text wrapping, and selected reference
links.
  * Updated documentation authoring and review guidance.

* **Chores**
* Retired automated MDX linting from workflows and local validation
commands.
* Removed lint-suppression markers throughout documentation without
changing instructions.
  * Added targeted documentation review guidance for pull requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-22 10:00:41 -07:00

460 lines
18 KiB
Plaintext

---
title: 'Configure Social Login (OAuth) Providers'
description: 'Set up social login (OAuth/OIDC) providers for self-hosted Supabase with Docker.'
subtitle: 'Set up social login (OAuth/OIDC) providers for self-hosted Supabase with Docker.'
---
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.
## Before you begin
You need:
- A working self-hosted Supabase installation. See [Self-Hosting with Docker](/docs/guides/self-hosting/docker).
- `API_EXTERNAL_URL` set to the publicly reachable URL of your Supabase instance (e.g., `https://<your-domain>/auth/v1`).
<Admonition type="danger">
HTTPS is required by most OAuth providers - `http://` callback URLs are rejected (except `localhost`). See the HTTPS [how-to guide](/docs/guides/self-hosting/self-hosted-proxy-https) for setup instructions.
</Admonition>
Your **OAuth callback URL** should look like the following:
```
https://<your-domain>/auth/v1/callback
```
You will have to register this URL with each OAuth provider.
## OAuth request flow
When a user signs in with an OAuth provider, the following flow occurs:
1. Your app calls `supabase.auth.signInWithOAuth()` and the browser redirects to the Auth service
2. The API gateway routes the request to the Auth container (`/auth/v1/authorize`)
3. Auth redirects the user to the OAuth provider (e.g., Google) for consent
4. The provider redirects back to `https://<your-domain>/auth/v1/callback`
5. Auth exchanges the authorization code for tokens and redirects the user to your `SITE_URL` or an allowed redirect URL
## Auth environment variables
The 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_ENABLED`
- `GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID`
- `GOTRUE_EXTERNAL_GOOGLE_SECRET`
- `GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI`
## Step-by-step configuration
The default `.env.example` and `docker-compose.yml` include commented-out placeholders for Google, GitHub, and Azure.
### Step 1: Register your app with the provider
1. Go to the OAuth provider's developer console and create an application.
2. Set the **authorized redirect URL**, e.g., `https://<your-domain>/auth/v1/callback`
3. Copy the **client ID** and **client secret** into your `.env` file.
### Step 2: Configure environment variables
Uncomment the lines for your provider in `.env` and add your client ID and secret, e.g., for Google:
```sh
GOOGLE_ENABLED=true
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_SECRET=your-client-secret
```
### Step 3: Enable the matching lines in Docker Compose configuration
Uncomment the corresponding `GOTRUE_EXTERNAL_` lines in the `auth` service's `environment`:
```yaml name=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}/callback
```
<Admonition type="note">
For providers **not** pre-configured in the files (see the [full provider list](#other-supported-providers) below), add the lines manually following the same pattern: variables in `.env`, passthrough with `GOTRUE_EXTERNAL_PROVIDER_` in `docker-compose.yml`.
</Admonition>
### Step 4: Restart the auth service
```sh
sh run.sh recreate auth
```
### Step 5: Verify the configuration
Check that the provider is enabled:
```sh
curl -H 'apikey: your-supabase-publishable-key' https://<your-domain>/auth/v1/settings
```
The response should include your provider under `external`:
```json
{
"external": {
"google": true
}
}
```
## Provider-specific setup
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="google"
>
<TabPanel id="google" label="Google">
**Google Cloud Console setup:**
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create or select a project
3. Select **Solutions** > **All products** in the navigation menu on the left
4. Go to **APIs & services** > **OAuth consent screen** and click **Get started**
5. Follow the configuration steps and add an **External** app
6. Go to **APIs & Services** > **Credentials**
7. Click **Create Credentials** > **OAuth client ID**
8. Set application type to **Web application**
9. Under **Authorized redirect URIs**, add: `https://<your-domain>/auth/v1/callback`
10. Click **Create** and copy the client ID and client secret
```sh name=.env
GOOGLE_ENABLED=true
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_SECRET=your-google-client-secret
```
```yaml name=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}/callback
```
</TabPanel>
<TabPanel id="github" label="GitHub">
**GitHub setup:**
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Click **New OAuth app**
3. Fill in **Homepage URL**, e.g., `https://<your-domain>`
4. Set **Authorization callback URL** to: `https://<your-domain>/auth/v1/callback`
5. Click **Register application**
6. Copy the client ID, generate and copy a client secret
```sh name=.env
GITHUB_ENABLED=true
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_SECRET=your-github-client-secret
```
```yaml name=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}/callback
```
</TabPanel>
<TabPanel id="azure" label="Azure">
**Azure Portal setup:**
1. Go to [Azure Portal](https://portal.azure.com/)
2. Go to **All services** > **Identity** > **App registrations** via the navigation menu on the left
3. Click **New registration**
4. Add application **Name**
5. Under **Redirect URI**, select **Web** and enter: `https://<your-domain>/auth/v1/callback`
6. Click **Register**
7. Copy the **Application (client) ID**
8. Click on **Client credentials** > **Add a certificate or secret**
9. Click on **New client secret** and add a client secret
10. Copy the secret value (not "secret ID")
```sh name=.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
```
```yaml name=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}/callback
## Optional: uncomment for tenant-specific Azure login
# GOTRUE_EXTERNAL_AZURE_URL: ${AZURE_URL}
```
</TabPanel>
<TabPanel id="apple" label="Apple">
**Apple Developer setup:**
1. Refer to [Apple Developer documentation](https://developer.apple.com/documentation/signinwithapple/configuring-your-environment-for-sign-in-with-apple) to learn how to enable App ID and create a Services ID
2. Create a private key for sign in with Apple
3. Generate a client secret JWT from your private key. See [Apple Developer documentation](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret) for details.
```sh name=.env
APPLE_ENABLED=true
APPLE_CLIENT_ID=com.example.your-services-id
APPLE_SECRET=your-generated-jwt-client-secret
```
```yaml name=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}/callback
```
<Admonition type="note">
Apple uses `response_mode=form_post` for its OAuth flow. The Auth service handles this automatically - no additional configuration is needed.
</Admonition>
</TabPanel>
<TabPanel id="keycloak" label="Keycloak">
**Keycloak setup:**
1. Open your Keycloak admin console
2. Select (or create) the realm you want to use
3. Go to **Clients** > **Create client**
4. Set **Client type** to **OpenID Connect**
5. Set **Client ID** (e.g., `supabase`)
6. On the next screen, enable **Client authentication**
7. Under **Valid redirect URIs**, add: `https://<your-domain>/auth/v1/callback`
8. Save, then go to the **Credentials** tab and copy the **Client secret**
```sh name=.env
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
```
```yaml name=docker-compose.yml
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}/callback
GOTRUE_EXTERNAL_KEYCLOAK_URL: ${KEYCLOAK_URL}
```
<Admonition type="danger">
`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 sign-in will not work.
</Admonition>
</TabPanel>
</Tabs>
## Other supported providers
Supabase Auth supports the following OAuth providers:
| Provider | Env prefix | Additional variables | Docs |
| ----------------- | ---------------- | ------------------------------- | ----------------------------------------------------------------------- |
| Apple | `APPLE_` | - | [Sign in with Apple](/docs/guides/auth/social-login/auth-apple) |
| Azure (Microsoft) | `AZURE_` | `URL` (tenant URL) | [Sign in with Azure](/docs/guides/auth/social-login/auth-azure) |
| Bitbucket | `BITBUCKET_` | - | [Sign in with Bitbucket](/docs/guides/auth/social-login/auth-bitbucket) |
| Discord | `DISCORD_` | - | [Sign in with Discord](/docs/guides/auth/social-login/auth-discord) |
| Facebook | `FACEBOOK_` | - | [Sign in with Facebook](/docs/guides/auth/social-login/auth-facebook) |
| Figma | `FIGMA_` | - | [Sign in with Figma](/docs/guides/auth/social-login/auth-figma) |
| GitHub | `GITHUB_` | `URL` (for GitHub Enterprise) | [Sign in with GitHub](/docs/guides/auth/social-login/auth-github) |
| GitLab | `GITLAB_` | `URL` (for self-hosted GitLab) | [Sign in with GitLab](/docs/guides/auth/social-login/auth-gitlab) |
| Google | `GOOGLE_` | - | [Sign in with Google](/docs/guides/auth/social-login/auth-google) |
| Kakao | `KAKAO_` | - | [Sign in with Kakao](/docs/guides/auth/social-login/auth-kakao) |
| Keycloak (OIDC) | `KEYCLOAK_` | `URL` (realm URL, **required**) | [Sign in with Keycloak](/docs/guides/auth/social-login/auth-keycloak) |
| LinkedIn (OIDC) | `LINKEDIN_OIDC_` | - | [Sign in with LinkedIn](/docs/guides/auth/social-login/auth-linkedin) |
| Notion | `NOTION_` | - | [Sign in with Notion](/docs/guides/auth/social-login/auth-notion) |
| Slack (OIDC) | `SLACK_OIDC_` | - | [Sign in with Slack](/docs/guides/auth/social-login/auth-slack) |
| Snapchat | `SNAPCHAT_` | - | - |
| Spotify | `SPOTIFY_` | - | [Sign in with Spotify](/docs/guides/auth/social-login/auth-spotify) |
| Twitch | `TWITCH_` | - | [Sign in with Twitch](/docs/guides/auth/social-login/auth-twitch) |
| Twitter | `TWITTER_` | - | [Sign in with Twitter](/docs/guides/auth/social-login/auth-twitter) |
| WorkOS | `WORKOS_` | - | [Sign in with WorkOS](/docs/guides/auth/social-login/auth-workos) |
| Zoom | `ZOOM_` | - | [Sign in with Zoom](/docs/guides/auth/social-login/auth-zoom) |
For each provider, you need at minimum `ENABLED`, `CLIENT_ID`, `SECRET`, and `REDIRECT_URI` in `.env` and `docker-compose.yml`.
<Admonition type="caution">
**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`.
</Admonition>
## Test the sign-in flow
You can test OAuth with the following minimal HTML page:
- Save the code below to `index.html`
- Start `python -m http.server 3000` in the same directory
- Make sure `SITE_URL` is set to `http://localhost:3000` in your self-hosted Supabase `.env` configuration
- Open your browser and go to `http://localhost:3000`
```html
<!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_PUBLISHABLE_KEY = 'your-supabase-publishable-key'
const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_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](/docs/guides/auth/social-login).
## Troubleshooting
### "Provider not enabled" or provider seen as false in settings
- Check that `GOTRUE_EXTERNAL_*_ENABLED` is set to `true` in `docker-compose.yml`
- Verify the `.env` variable is not empty, e.g., check with `sh run.sh printenv auth | grep GOOGLE`
### Variables added to the environment but provider still not working
Configuration 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:
```yaml name=docker-compose.yml
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
```
Run `sh run.sh printenv auth | grep GOTRUE_EXTERNAL` to verify the variables are reaching the container.
### Site URL or redirect URL errors after sign-in
After a successful OAuth sign-in, 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 URL**
- If your app uses a different redirect URL, add it to `ADDITIONAL_REDIRECT_URLS` (comma-separated)
### Nonce check failure on mobile (Google Sign In)
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:
- Enable it only in the environment where you're debugging the issue.
- Revert it as soon as the issue is resolved.
- Prefer fixing the client-side nonce handling or switching to an OAuth flow (authorization code with PKCE) that avoids ID-token nonce issues entirely.
</Admonition>
To enable it, uncomment the following line in `docker-compose.yml`:
```yaml name=docker-compose.yml
auth:
environment:
# ... existing variables ...
GOTRUE_EXTERNAL_SKIP_NONCE_CHECK: 'true'
```
### Auth service fails to start
Check the auth container logs:
```sh
docker compose logs auth
```
Common causes:
- Missing required environment variable (e.g., `CLIENT_ID` or `SECRET` is empty)
- Invalid `API_EXTERNAL_URL` (must be a valid URL - including protocol and ending with `/auth/v1`)
## Environment variable reference
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}/callback` | Yes |
| `GOTRUE_SITE_URL` | Default redirect URL after authentication (set via `SITE_URL` in `.env`) | Yes |
## Additional resources
- [Redirect URLs](/docs/guides/auth/redirect-urls)
- [Auth server on GitHub](https://github.com/supabase/auth) (check README and `example.env`)