Skip to main content
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/mobile clients. For the auth foundation, see Auth model. For email/password and magic-link flows, see Signup, signin, magic link.

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:
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 — useful when the provider is self-hosted or you’re using 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 transparently. 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, 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:
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

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);
  }
}

Setting up GitHub

Same shape, different provider console.

1. GitHub OAuth app setup

In GitHub Developer Settings, 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

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:
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 — e.g., to read a GitHub user’s repos or send email through Gmail — pass scopes as a query parameter:
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:
// 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:
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

Signup, signin, magic link

Email/password and magic-link flows as the alternative to OAuth.

Auth model

What’s inside the JWTs you get back from these flows.

Auth Reference

Full /auth/v1/* endpoint surface, including the OAuth-specific endpoints.

RLS Cookbook

How to use OAuth-provided claims (provider, provider_id) in RLS policies.