> ## Documentation Index
> Fetch the complete documentation index at: https://docs.powabase.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth providers

> Wire up Google, GitHub, Apple, and 19 other OAuth providers with PKCE. Configuration, redirect URLs, the sign-in flow, and the gotchas that bite people.

GoTrue ships with 22 OAuth providers wired up, all disabled by default. Enabling one is a configuration change plus app code to drive the redirect flow. This guide covers the full sequence for Google and GitHub (the two most common), points you at the per-provider docs for the rest, and explains how PKCE works for browser and mobile clients.

For the auth foundation, see [Auth model](/concepts/auth-model). For email/password and magic-link flows, see [Signup, signin, magic link](/guides/auth-signup-signin).

## Supported providers

The 22 OAuth providers GoTrue ships with on Powabase:

`apple` · `azure` · `bitbucket` · `discord` · `facebook` · `figma` · `github` · `gitlab` · `google` · `kakao` · `keycloak` · `linkedin_oidc` · `notion` · `slack` · `slack_oidc` · `spotify` · `twitch` · `twitter` · `workos` · `zoom`

All of them are off by default. You enable them with provider-specific environment variables (Helm overrides for self-hosted, or the Studio's auth settings for managed). The pattern for any provider is:

```yaml theme={null}
gotrue:
  providers:
    google:
      enabled: "true"
      clientId: "<your-client-id>"
      secret: "<your-client-secret>"
```

A few providers (`azure`, `gitlab`, `keycloak`, `workos`) also need a `url` field for their issuer endpoint, which matters when the provider is self-hosted or you're on a non-default tenant.

## The redirect flow at a glance

1. Your app calls `GET /auth/v1/authorize?provider=google&redirect_to=<your-callback>`.
2. GoTrue 302-redirects the browser to Google's consent screen with the client\_id, scopes, and a state parameter it generated.
3. The user authorizes; Google redirects back to GoTrue's callback URL (`<project>/auth/v1/callback`) with an authorization code.
4. GoTrue exchanges the code with Google for the user's profile, creates/updates the user record, and 302-redirects the browser to your `redirect_to` URL with a session code in the URL fragment.
5. Your callback page extracts the tokens from the URL fragment (or, in PKCE mode, exchanges the code for tokens) and persists them.

Most of this happens behind the scenes. Your code is only step 1 (kick off the flow) and step 5 (handle the callback).

## Setting up Google

Three pieces: register a Google Cloud OAuth client, configure GoTrue, write the app code.

### 1. Google Cloud setup

In the [Google Cloud Console](https://console.cloud.google.com/apis/credentials), create an OAuth 2.0 Client ID. The fields that matter:

* **Application type:** Web application.
* **Authorized JavaScript origins:** the origin of your app (e.g., `https://your-app.example.com`).
* **Authorized redirect URIs:** `https://{ref}.p.powabase.ai/auth/v1/callback`. This is GoTrue's callback, **not** your app's. Google will call GoTrue, GoTrue will call your app.

Note the **client ID** and **client secret**.

### 2. Enable in Powabase

For managed cloud, set `Google Client ID` and `Google Client Secret` in the Studio's Authentication settings. For self-hosted:

```yaml theme={null}
gotrue:
  providers:
    google:
      enabled: "true"
      clientId: "your-google-client-id.apps.googleusercontent.com"
      secret: "GOCSPX-your-client-secret"
  # Make sure your app's callback is in the allow list:
  uriAllowList: "https://your-app.example.com/auth/callback,http://localhost:3000/auth/callback"
```

Apply the Helm release and GoTrue will restart with Google enabled.

### 3. App code

<CodeGroup>
  ```typescript TypeScript theme={null}
  const BASE_URL = "https://{ref}.p.powabase.ai";
  const ANON_KEY = "<your anon key>";

  // Step 1: Kick off the OAuth flow
  function signInWithGoogle() {
    const redirectTo = encodeURIComponent("https://your-app.example.com/auth/callback");
    window.location.href = `${BASE_URL}/auth/v1/authorize?provider=google&redirect_to=${redirectTo}`;
  }

  // Step 5: On the /auth/callback page, handle the redirect from GoTrue
  // Tokens come back in the URL fragment, not the query string.
  function handleCallback() {
    const params = new URLSearchParams(window.location.hash.slice(1));
    const access_token = params.get("access_token");
    const refresh_token = params.get("refresh_token");
    if (access_token && refresh_token) {
      localStorage.setItem("powabase_access_token", access_token);
      localStorage.setItem("powabase_refresh_token", refresh_token);
      window.location.hash = ""; // clean the URL
      window.location.replace("/"); // navigate to your app
    } else {
      const error = params.get("error_description") ?? "unknown";
      console.error("Sign-in failed:", error);
    }
  }
  ```

  ```python Python theme={null}
  # OAuth is intrinsically a browser flow — there's no clean way to drive it
  # entirely from a server. If you need server-side sign-in, use email +
  # password (see guides/auth-signup-signin) or roll a custom flow with the
  # admin API.
  #
  # What you can do server-side: render an HTML page with the sign-in button:
  authorize_url = f"{BASE_URL}/auth/v1/authorize?provider=google&redirect_to={your_callback_url}"
  print(f'<a href="{authorize_url}">Sign in with Google</a>')
  ```

  ```bash cURL theme={null}
  # OAuth flows happen in the browser, not via cURL. You can fetch the
  # /authorize endpoint to confirm the redirect URL works, but the
  # actual sign-in needs a browser to follow the redirects.
  curl -I 'https://{ref}.p.powabase.ai/auth/v1/authorize?provider=google&redirect_to=https://your-app.example.com/auth/callback' \
    -H "apikey: <ANON_KEY>"
  # Expected: 302 with a Location: header pointing to Google.
  ```
</CodeGroup>

## Setting up GitHub

Same shape, different provider console.

### 1. GitHub OAuth app setup

In [GitHub Developer Settings](https://github.com/settings/developers), create a new OAuth App:

* **Homepage URL:** your app's URL.
* **Authorization callback URL:** `https://{ref}.p.powabase.ai/auth/v1/callback`. GoTrue's callback, not your app's.

Note the **client ID** and generate a **client secret**.

### 2. Enable in Powabase

```yaml theme={null}
gotrue:
  providers:
    github:
      enabled: "true"
      clientId: "Iv1.xxx"
      secret: "ghp_xxx"
  uriAllowList: "https://your-app.example.com/auth/callback"
```

### 3. App code

Same as Google, just change `provider=google` to `provider=github`:

```typescript theme={null}
window.location.href = `${BASE_URL}/auth/v1/authorize?provider=github&redirect_to=${redirectTo}`;
```

## Requesting additional scopes

By default, GoTrue requests the minimum scopes needed to identify the user (typically email + profile). To request more, say to read a GitHub user's repos or send email through Gmail, pass `scopes` as a query parameter:

```typescript theme={null}
const scopes = encodeURIComponent("repo read:org");
window.location.href =
  `${BASE_URL}/auth/v1/authorize?provider=github&scopes=${scopes}&redirect_to=${redirectTo}`;
```

The provider's access token (the one that lets you call Google or GitHub APIs on the user's behalf) lands in `app_metadata.provider_token` on the user record. Read it from `auth.jwt() -> 'app_metadata' ->> 'provider_token'` in SQL, or from the `user` object in your client SDK.

GoTrue does **not** refresh provider tokens for you. If you need long-lived API access to the upstream provider, you'll need to handle the refresh against that provider's token endpoint yourself.

## PKCE: when and why

For public clients (browser SPAs, mobile apps), the standard OAuth flow has a vulnerability: the authorization code is transmitted via URL parameters back to your app, and any local code that intercepts that URL can exchange the code for tokens. PKCE (Proof Key for Code Exchange) fixes this by having your client generate a one-time secret at the start of the flow and prove possession of it during the code exchange. An intercepted code is useless without the secret.

GoTrue supports PKCE automatically when you pass a `code_challenge` parameter to `/auth/v1/authorize`. Most client SDKs (e.g., `supabase-js`) handle PKCE transparently. If you're rolling your own client:

```typescript theme={null}
// Generate a code verifier (random 43-128 char string)
function generateCodeVerifier(): string {
  const arr = new Uint8Array(32);
  crypto.getRandomValues(arr);
  return btoa(String.fromCharCode(...arr))
    .replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}

// Hash it to get the challenge
async function deriveChallenge(verifier: string): Promise<string> {
  const data = new TextEncoder().encode(verifier);
  const hash = await crypto.subtle.digest("SHA-256", data);
  return btoa(String.fromCharCode(...new Uint8Array(hash)))
    .replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}

async function signInWithPkce() {
  const verifier = generateCodeVerifier();
  const challenge = await deriveChallenge(verifier);
  // Persist the verifier so we can use it on the callback page
  sessionStorage.setItem("pkce_verifier", verifier);

  const params = new URLSearchParams({
    provider: "google",
    redirect_to: "https://your-app.example.com/auth/callback",
    code_challenge: challenge,
    code_challenge_method: "S256",
  });
  window.location.href = `${BASE_URL}/auth/v1/authorize?${params}`;
}

// On the callback page:
async function handlePkceCallback() {
  const params = new URLSearchParams(window.location.search);
  const code = params.get("code");
  const verifier = sessionStorage.getItem("pkce_verifier");
  if (!code || !verifier) {
    console.error("Missing code or verifier");
    return;
  }

  const res = await fetch(`${BASE_URL}/auth/v1/token?grant_type=pkce`, {
    method: "POST",
    headers: {
      apikey: ANON_KEY,
      Authorization: `Bearer ${ANON_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ auth_code: code, code_verifier: verifier }),
  });
  const result = await res.json();
  sessionStorage.removeItem("pkce_verifier");
  // Persist tokens as usual
}
```

When you use PKCE, GoTrue returns the auth code in the query string (not the URL fragment), and your app explicitly exchanges it. When you don't use PKCE, GoTrue returns tokens directly in the URL fragment after the OAuth dance is complete.

For browser SPAs, **use PKCE.** For server-rendered apps where the callback is server-side and tokens never touch the browser, the standard flow is fine.

## The redirect-allow-list gotcha

The `gotrue.uriAllowList` is a comma-separated list of URLs GoTrue will redirect to. **If your `redirect_to` isn't in the list, GoTrue silently drops it** and redirects to `gotrue.siteUrl` instead, which on a fresh project is unset, so you end up on the GoTrue host with no obvious error.

Set the allow list to include every callback URL your app might use:

```yaml theme={null}
gotrue:
  siteUrl: "https://your-app.example.com"
  uriAllowList: "https://your-app.example.com/auth/callback,http://localhost:3000/auth/callback,http://localhost:3000/auth/reset-password"
```

For local development, include the localhost variants. Don't add wildcards; GoTrue does substring matching, not glob.

## Common failure modes

* **"redirect\_uri\_mismatch" from the provider.** The callback URL you registered with Google/GitHub doesn't match `https://{ref}.p.powabase.ai/auth/v1/callback`. Update the provider's callback URL.
* **Redirect lands on GoTrue host, not your app.** Your `redirect_to` isn't in the `uriAllowList`. Add it.
* **`error=server_error` with no detail.** GoTrue couldn't exchange the code with the provider. Most often: the provider's client secret is wrong in your Helm config, or the provider has rate-limited your client ID. Check the GoTrue pod logs.
* **User signs in but lands without `app_metadata.provider_token`.** Some providers need explicit scopes to return their token (e.g., Google's `offline_access`). Pass `scopes=offline_access` in the `/authorize` query.

## Next steps

<CardGroup cols={2}>
  <Card title="Signup, signin, magic link" href="/guides/auth-signup-signin">
    Email/password and magic-link flows as the alternative to OAuth.
  </Card>

  <Card title="Auth model" href="/concepts/auth-model">
    What's inside the JWTs you get back from these flows.
  </Card>

  <Card title="Auth Reference" href="/api-reference/auth">
    Full /auth/v1/\* endpoint surface, including the OAuth-specific endpoints.
  </Card>

  <Card title="RLS Cookbook" href="/guides/rls-policies">
    How to use OAuth-provided claims (provider, provider\_id) in RLS policies.
  </Card>
</CardGroup>
