All Products
Search
Document Center

AnalyticDB:Enterprise SSO integration with Supabase

Last Updated:Apr 10, 2026

Cloud-native data warehouse AnalyticDB for PostgreSQL integrates with the open-source Supabase authentication system. This enables enterprises to configure SAML 2.0 single sign-on (SSO), making it ideal for building private Coding Agent platforms. This document explains how to configure SAML SSO for a Supabase project to unify identity authentication for enterprise users.

Prerequisites

Procedure

Step 1: Obtain Supabase project information

After creating a Supabase project, you can obtain the following information.

Item

Description

Example

Supabase URL

The access URL of your Supabase project.

https://spb-xxxxx.supabase.example.com

Anon Key (public key)

A public key that can be safely used in your frontend code.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Service Role Key (secret key)

A secret key for backend use only. It is used for administrative operations. Never expose this key in your frontend code.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Verify project status

curl 'https://<your-supabase-url>/auth/v1/health' \
  -H 'apikey: <your-anon-key>'

Example response

{"version":"vunspecified","name":"GoTrue","description":"GoTrue is a user registration and authentication API"}

Step 2: Obtain SP metadata

The metadata for the SAML service provider (SP) is publicly accessible at the following URL:

https://<your-supabase-url>/sso/saml/metadata

You will need the following two URLs to configure your IdP:

URL type

Value

ACS URL

https://<your-supabase-url>/sso/saml/acs

Entity ID

https://<your-supabase-url>/sso/saml/metadata

Step 3: Configure redirect URL

  1. Log in to the Supabase Dashboard. In the sidebar of the Dashboard, click Authentication > URL Configuration to navigate to the Site URL configuration page.

  2. In the Site URL section, enter your frontend domain URL, and then click Save changes.

Step 4: Register identity provider (IdP)

Register your IdP with Supabase using the Management API.

Method 1: Register via metadata URL

If your IdP provides a public metadata URL (as most do), run the following command:

curl -X POST 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "saml",
    "metadata_url": "https://<your-idp>/samlp/metadata/<client-id>",
    "domains": ["yourdomain.com"]
  }'

Method 2: Register via metadata XML

If the metadata URL is not publicly accessible, you can provide the XML content directly:

# 1. Obtain the metadata XML from an environment that can access the IdP.
curl 'https://<your-idp>/samlp/metadata/<client-id>' > idp-metadata.xml

# 2. Register with Supabase.
curl -X POST 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "saml",
    "metadata_xml": "<EntityDescriptor ...>...</EntityDescriptor>",
    "domains": ["yourdomain.com"]
  }'

Example: Auth0 registration

The Auth0 metadata URL has the following format:

https://<tenant>.auth0.com/samlp/metadata/<client-id>

The complete registration command is as follows:

curl -X POST 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "saml",
    "metadata_url": "https://dev-xxx.auth0.com/samlp/metadata/YOUR_CLIENT_ID",
    "domains": ["example.com"]
  }'

Save returned provider ID

Important

Save the id field from the response. This ID is required when initiating a sign-in request from the frontend.

The following is an example response for a successful registration:

{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "type": "saml",
  "saml": {
    "entity_id": "urn:dev-xxx.auth0.com"
  },
  "domains": [{"domain": "example.com"}]
}

Step 5: Configure SP information in IdP

After registration, configure the Supabase SP information in your IdP.

Required configuration items

Parameter

Value

ACS URL (callback URL)

https://<your-supabase-url>/sso/saml/acs

Entity ID (SP Entity ID)

https://<your-supabase-url>/sso/saml/metadata

NameID Format

emailAddress

Frontend callback URL

https://<your-app-domain>

Example: Configuration steps for Auth0

  1. Log in to the Auth0 Dashboard.

  2. Go to Applications and click the target application.

  3. Click the Addons tab and enable SAML2 Web App.

  4. In the dialog box that appears, configure the following settings:

    • Application Callback URL

    • Settings

  5. Click Enable, and then click Save.

  6. On the application's main page, add your frontend URL to Allowed Callback URLs.

Step 6: Frontend integration

  1. Install the dependency.

    npm install @supabase/supabase-js
  2. Initialize the client. SAML SSO must use the implicit flow.

    import { createClient } from '@supabase/supabase-js'
    
    const supabase = createClient(
      'https://<your-supabase-url>',
      '<your-anon-key>',
      {
        auth: {
          flowType: 'implicit',
          detectSessionInUrl: true,
          persistSession: true,
          autoRefreshToken: true,
        }
      }
    )
  3. Initiate SSO sign-in.

    • Initiate sign-in using the provider ID:

      const { data, error } = await supabase.auth.signInWithSSO({
        providerId: '<your-provider-id>',
        options: {
          redirectTo: window.location.origin
        }
      })
      
      if (error) {
        console.error('Sign-in failed:', error.message)
        return
      }
      
      if (data?.url) {
        window.location.href = data.url
      }
    • Alternatively, automatically match the provider using a domain:

      const { data, error } = await supabase.auth.signInWithSSO({
        domain: 'yourdomain.com'
      })
  4. Handle the sign-in callback.

    After the IdP authenticates the user, it redirects back to the frontend. The URL has the following format:

    https://<your-app-domain>/#access_token=<jwt>&refresh_token=<token>&expires_in=3600&token_type=bearer

    Because the supabase-js initialization is asynchronous, we recommend that you manually parse the URL hash:

    function decodeJWT(token) {
      try {
        return JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')))
      } catch {
        return null
      }
    }
    
    async function init() {
      const hash = window.location.hash.substring(1)
      if (hash.includes('access_token')) {
        const params = new URLSearchParams(hash)
        const accessToken = params.get('access_token')
        const refreshToken = params.get('refresh_token')
        const expiresAt = params.get('expires_at')
        
        window.history.replaceState({}, '', window.location.pathname)
        
        const payload = decodeJWT(accessToken)
        if (payload) {
          const session = {
            access_token: accessToken,
            refresh_token: refreshToken || '',
            expires_at: expiresAt ? parseInt(expiresAt) : payload.exp,
            token_type: 'bearer',
            user: {
              id: payload.sub,
              email: payload.email,
              role: payload.role,
              app_metadata: payload.app_metadata || {},
              user_metadata: payload.user_metadata || {},
            }
          }
          onLoginSuccess(session)
          return
        }
      }
    
      const { data: { session } } = await supabase.auth.getSession()
      if (session) {
        onLoginSuccess(session)
      } else {
        showLoginPage()
      }
    }
    
    document.addEventListener('DOMContentLoaded', init)
  5. View the JWT payload structure.

    After a successful sign-in, the decoded JWT contains the following key fields:

    {
      "sub": "8c308927-4ba1-4507-b6f8-f751b791****",
      "email": "user@example.com",
      "role": "authenticated",
      "aal": "aal1",
      "amr": [{
        "method": "sso/saml",
        "timestamp": 1774928127,
        "provider": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }],
      "app_metadata": {
        "provider": "sso:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      },
      "session_id": "aeaf9cdb-dbcd-46ab-95a5-ff956e69****"
    }

    You can confirm that the user signed in through SSO by checking if amr[0].method === 'sso/saml'.

Complete example project

Here is a minimal, runnable example. For an online demo, see sso-demo.

  • main.js

    import { createClient } from '@supabase/supabase-js'
    
    const SUPABASE_URL = 'https://<your-supabase-url>'
    const SUPABASE_ANON_KEY = '<your-anon-key>'
    const AUTH0_PROVIDER_ID = '<auth0-provider-id>'
    
    const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
      auth: { flowType: 'implicit', detectSessionInUrl: true }
    })
    
    function decodeJWT(token) {
      try {
        return JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')))
      } catch { return null }
    }
    
    window.signInWithAuth0 = async () => {
      const { data, error } = await supabase.auth.signInWithSSO({
        providerId: AUTH0_PROVIDER_ID,
        options: { redirectTo: window.location.origin }
      })
      if (error) return alert(error.message)
      if (data?.url) window.location.href = data.url
    }
    
    window.signOut = async () => {
      await supabase.auth.signOut()
      window.location.reload()
    }
    
    async function init() {
      const hash = window.location.hash.substring(1)
      
      if (hash.includes('access_token')) {
        const p = new URLSearchParams(hash)
        const token = p.get('access_token')
        const payload = decodeJWT(token)
        window.history.replaceState({}, '', window.location.pathname)
        
        if (payload) {
          showUser({ token, payload, refresh_token: p.get('refresh_token') })
          return
        }
      }
      
      const { data: { session } } = await supabase.auth.getSession()
      if (session) {
        showUser({ token: session.access_token, payload: decodeJWT(session.access_token) })
      }
    }
    
    function showUser({ token, payload }) {
      document.getElementById('login').style.display = 'none'
      document.getElementById('user').innerHTML = `
        <p><strong>Email: </strong>${payload.email}</p>
        <p><strong>User ID: </strong>${payload.sub}</p>
        <p><strong>Authentication Method: </strong>${payload.amr?.[0]?.method}</p>
        <button onclick="signOut()">Sign out</button>
      `
    }
    
    document.addEventListener('DOMContentLoaded', init)
  • index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>SSO Login</title>
    </head>
    <body>
      <div id="login">
        <button onclick="signInWithAuth0()">Sign in with Auth0</button>
      </div>
      <div id="user"></div>
      <script type="module" src="/main.js"></script>
    </body>
    </html>

The code above implements an Auth0 SSO sign-in flow.

You can view SSO user records in the auth.users table of your Supabase project. The is_sso_user field for these users is set to TRUE.

Manage providers

You can use the following API operations to add, delete, or manage SSO providers. All of these operations require the service role key.

List providers

curl 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>'

You can also view the auth.sso_domains and auth.sso_providers tables in the Supabase Dashboard to confirm the registered SSO providers.

Get provider

curl 'https://<your-supabase-url>/auth/v1/admin/sso/providers/<provider-id>' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>'

Update provider

curl -X PUT 'https://<your-supabase-url>/auth/v1/admin/sso/providers/<provider-id>' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>' \
  -H 'Content-Type: application/json' \
  -d '{"metadata_url": "https://<your-idp>/samlp/metadata/<client-id>"}'

Delete provider

curl -X DELETE 'https://<your-supabase-url>/auth/v1/admin/sso/providers/<provider-id>' \
  -H 'apikey: <your-service-role-key>' \
  -H 'Authorization: Bearer <your-service-role-key>'

FAQ

Page does not redirect after sign-in

Confirm that flowType: 'implicit' is set in the createClient configuration. The default Proof Key for Code Exchange (PKCE) flow may cause issues in SAML callback scenarios.

Sign-in page remains after callback

The SDK initializes asynchronously, so you must manually parse the access_token from the URL hash when the DOMContentLoaded event occurs. Do not rely solely on supabase.auth.getSession() to handle the callback.

Sign-in failed: No such SSO provider

  1. Check whether the providerId in your frontend code matches the ID that was returned during registration.

  2. Perform a hard refresh of your browser (Cmd+Shift+R or Ctrl+Shift+R) to clear the cache.

Sign-in failed: Failed to fetch

  1. Check your network connection to the Supabase project.

  2. Run a test command in the browser console to verify connectivity.

Incorrect redirect after sign-in

Explicitly specify the frontend URL in the options.redirectTo parameter of the signInWithSSO() function. This parameter overrides the instance's default Site URL.

SAML RelayState has expired

The SSO sign-in URL is time-sensitive and typically expires in a few minutes. Call signInWithSSO() for each new sign-in attempt. Do not reuse an old sign-in URL.

Auth0 error: invalid_request

Check that Allowed Callback URLs in your Auth0 settings includes your frontend domain, and that the Application Callback URL is set to the Supabase ACS URL.