Enabling custom SAML SSO on Your Self-hosted Supabase

Calvin,5 min read

I use Supabase and refine.js for my latest client project. Supabase is a very well designed and easy to use backend suite, with a very active development team. As required by the client, I need to run the software suite on-premise. While developing with Supabase for authentication and data management is a walk in the park, one tricky issue is that SSO (Single Sign On) with custom SAML Identify Provider is not enabled by default. Although Supabase supports SSO feature for their hosted service, it is not clearly documented how to enable SSO when it comes to self-hosted deployment. This article is to document the steps I took to enable SSO for Supabase. I will also write another article later to show how to integrate the SSO feature with refine.js.

Prerequisite

Enable SAML in Supabase

Generate private key and add it to .env file

First, run the following command to generate a private key. A private key is required for the SAML Service Provider to sign the SAML response.

openssl genpkey -algorithm rsa -outform DER -out private_key.der

Then, convert it into base64 format and copy the output to .env file.

base64 -i private_key.der

Finally, put it in .env file at the same directory as /docker/docker-compose.yaml

.env
GOTRUE_SAML_ENABLED=true
GOTRUE_SAML_PRIVATE_KEY=(your private key in base64)

Update docker-compose.yaml to enable SAML

Next, we need to pass the new env vars to the docker container that runs the GoTrue auth service. Add the following to /docker/docker-compose.yaml.

docker-compose.yaml
  auth:
    container_name: supabase-auth
    image: supabase/gotrue:v2.99.0
    ...
    environment:
      ...
      GOTRUE_SAML_ENABLED: ${GOTRUE_SAML_ENABLED}
      GOTRUE_SAML_PRIVATE_KEY: ${GOTRUE_SAML_PRIVATE_KEY}
      ...

Expose the SAML endpoints in Kong

Referring to an issue (https://github.com/supabase/cli/issues/1335) in the Supabase git repo, we need to expose two SAML endpoints in Kong. The kong.yml file is located at /docker/volumes/api/kong.yml.

kong.xml
  ...
  ## Open SSO routes (by calvincchan)
  - name: auth-v1-open-sso-acs
    url: "http://auth:9999/sso/saml/acs"
    routes:
      - name: auth-v1-open-sso-acs
        strip_path: true
        paths:
        - /auth/v1/sso/saml/acs
        - /sso/saml/acs
    plugins:
      - name: cors
  - name: auth-v1-open-sso-metadata
    url: "http://auth:9999/sso/saml/metadata"
    routes:
      - name: auth-v1-open-sso-metadata
        strip_path: true
        paths:
        - /auth/v1/sso/saml/metadata
    plugins:
      - name: cors
  ...

The paths /auth/v1/sso/saml/acs and /sso/saml/acs seem to be redundant, but I have to add both of them for Auth0 to return to my site properly after login.

Optionally expose the new /sso/saml/acs path in the reverse proxy server (Nginx)

If you are deploying the suite to a public server behind a reverse proxy, you need to expose the new /sso/saml/acs path to docker. For example, if you are using nginx, you can add the following to your nginx config file (Assuming there is a “kong” upstream defined in the nginx config file)

nginx.conf
    # SSO
    location ~ ^/sso/(.*)$ {
        proxy_set_header Host $host;
        proxy_pass http://kong;
        proxy_redirect off;
    }

Restart the Nginx to apply the changes.

Test Supabase

Restart the containers by docker-compose down; docker-compose up -d. To test if the SAML endpoints are working, you can use the following API call:

API_KEY=(your supabase service role key);
curl -X GET http://localhost:8000/auth/v1/settings \
  -H 'APIKey: '"$API_KEY"'' \
  -H 'Authorization: Bearer '"$API_KEY"'';

You should be able to see "saml_enabled": true in the output:

{
  "external": {
    "apple": false,
    "azure": false,
    "bitbucket": false,
    ...
  },
  "disable_signup": false,
  "mailer_autoconfirm": false,
  "phone_autoconfirm": true,
  "sms_provider": "",
  "mfa_enabled": false,
  "saml_enabled": true
}

Add SAML identity provider to Supabase

In this section, let’s walk through how to add Auth0 as the SAML identity provider to Supabase. The steps are similar for other SAML identity providers.

  1. In Auth0, go to Applications > Applications > Create Application. Select “Single Page Web Applications” as the application type. Then, click “Create”.
  2. In the next page, select “Settings” tab. Under “Allowed Callback URLs”, add the following URL based on your deployment:
    • for local test: http://localhost:8000/sso/saml/acs
    • for production: https://yourdomain.com/sso/saml/acs
  3. In the same page and the same “Settings” tab, scroll down to “Advanced Settings” and click “Endpoints”. Copy the “SAML Metadata URL” to your clipboard.
  4. In your terminal, run the following command to add the SAML identity provider to Supabase:
API_KEY=(your supabase service role key);
curl -X POST http://localhost:8000/auth/v1/admin/sso/providers \
  -H 'APIKey: '"$API_KEY"'' \
  -H 'Authorization: Bearer '"$API_KEY"'' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "saml",
    "metadata_url": "(paste the SAML Metadata URL here)",
    "domains": ["yourdomain.auth0.com"]
  }';

If the command is successful, you should see the fetched metadata XML in the output.

To verify if the SAML identity provider is added successfully, you can run the following command:

API_KEY=(your supabase service role key);
curl -X GET http://localhost:8000/auth/v1/admin/sso/providers \
  -H 'APIKey: '"$API_KEY"'' \
  -H 'Authorization: Bearer '"$API_KEY"'' \

The output should look like:

{
  "items": [
    {
      "id": "2bc1c44f-ccea-4289-a736-4d6d333d4af0",
      "saml": {
        "entity_id": "urn:yourdomain.auth0.com",
        "metadata_url": "https://yourdomain.auth0.com/samlp/metadata/__auth0_client_id__",
        "attribute_mapping": {}
      },
      "domains": [{ "domain": "yourdomain.auth0.com" }],
      "created_at": "2023-12-07T19:34:09.907022Z",
      "updated_at": "2023-12-07T19:34:09.907022Z"
    }
  ]
}

Final step

After following the steps above, your Supabase instance should support sign-in through SSO. You can test it using the following API call:

API_KEY=(your supabase service role key);
curl -X GET http://localhost:8000/auth/v1/sso \
  -H 'APIKey: '"$API_KEY"'' \
  -H 'Authorization: Bearer '"$API_KEY"'' \
  -H 'Content-Type: application/json' \
  -d '{
    "domain": "yourdomain.auth0.com",
    "skip_http_redirect": true
  }';

The output should be a JSON with a url property. This is the URL to redirect the user to the external SSO login screen.

{
  "url": "https://dev-g8es4m0caprhrd4c.us.auth0.com/samlp/PU7tL448jcXfMpixiStfO3zzbmsBQktG?SAMLRequest=nJJPb9s8DIe%2FiqG7%2F8aNY6ExkDZ43wVIV69Jh2E3WmZirZbk..."
}

In the next article I will show how to add the SSO feature to a refine.js app.

References

Update at 2024-08-02