mirror of
https://github.com/supabase/supabase.git
synced 2026-06-01 10:21:10 +08:00
595 lines
21 KiB
Plaintext
595 lines
21 KiB
Plaintext
---
|
|
title: 'Configure SAML SSO'
|
|
description: 'Set up SAML 2.0 Single Sign-On for self-hosted Supabase with Docker.'
|
|
subtitle: 'Set up SAML 2.0 Single Sign-On for self-hosted Supabase with Docker.'
|
|
---
|
|
|
|
{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
|
|
SAML 2.0 SSO lets your users authenticate through an enterprise Identity Provider (IdP) such as Okta, Azure AD (Entra ID), Google Workspace, or any SAML 2.0-compliant provider. Unlike OAuth providers, SAML IdPs are not configured through environment variables - they are managed dynamically at runtime through the Auth admin API.
|
|
|
|
This guide covers the full setup: generating a signing key, enabling SAML in your Supabase instance, registering an IdP, and integrating SSO into your application.
|
|
|
|
<Admonition type="note">
|
|
|
|
Client-side integration uses the same `supabase.auth.signInWithSSO()` method documented in the [SSO with SAML 2.0](/docs/guides/auth/enterprise-sso/auth-sso-saml) guide. This guide focuses on the self-hosted server configuration.
|
|
|
|
</Admonition>
|
|
|
|
## Before you begin
|
|
|
|
You need:
|
|
|
|
- A running self-hosted Supabase instance (see the [setup guide](/docs/guides/self-hosting/docker))
|
|
- Open SSL installed (for key generation)
|
|
- Your IdP's SAML metadata URL or metadata XML
|
|
- The `SERVICE_ROLE_KEY` from your `.env` file (needed for admin API calls)
|
|
- `API_EXTERNAL_URL` set to the publicly-accessible URL of your Supabase Auth service (e.g., `https://<your-domain>`). This URL is used as the SAML Service Provider entity ID and for constructing the ACS endpoint URL
|
|
|
|
## How SAML SSO works in Supabase
|
|
|
|
SAML SSO is configured in two layers:
|
|
|
|
1. **Global SAML enable (environment variables)** - a small set of env vars that enable the SAML engine and provide a signing key. These go in `.env` and `docker-compose.yml`.
|
|
2. **Per-IdP configuration (admin API)** - individual Identity Providers are registered, updated, and deleted at runtime via the Auth admin API. No restart is needed when adding or removing providers.
|
|
|
|
The login flow works as follows:
|
|
|
|
1. Your app calls `POST /auth/v1/sso` with a domain or provider_id
|
|
2. Auth generates a SAML `AuthnRequest` and returns a redirect URL to the IdP
|
|
3. The user authenticates at the IdP
|
|
4. The IdP POSTs a SAML Response to `POST /sso/saml/acs`
|
|
5. Auth validates the assertion, creates or links the user, and issues a session
|
|
6. The user is redirected back to your app with session tokens
|
|
|
|
## Step 1: Generate an RSA private key
|
|
|
|
SAML requests must be signed. The value expected by `GOTRUE_SAML_PRIVATE_KEY` is a Base64-encoded PKCS#1 DER RSA private key (with a 2048-bit key as the minimum requirement). Generate it with:
|
|
|
|
```sh
|
|
openssl genpkey -algorithm RSA -out pk_pkcs8.pem -quiet && \
|
|
openssl pkey -in pk_pkcs8.pem -out pk_rsa1.der -outform DER -traditional && \
|
|
base64 -w 0 -i pk_rsa1.der
|
|
```
|
|
|
|
The commands above:
|
|
|
|
1. Generate an RSA private key in PKCS#8 PEM format
|
|
2. Convert the key to PKCS#1 DER format
|
|
3. Base64-encode the key for use as the value of `GOTRUE_SAML_PRIVATE_KEY`
|
|
|
|
Save the Base64 output - make sure to copy it as single line, ignoring the trailing newline. Remove the temporary files.
|
|
|
|
<Admonition type="caution">
|
|
|
|
Keep this key secret. Anyone with the private key can forge SAML requests on behalf of your Service Provider. Do not commit it to the version control system.
|
|
|
|
</Admonition>
|
|
|
|
<Admonition type="tip">
|
|
|
|
For a production deployment, consider using a 4096-bit key by adding `-pkeyopt rsa_keygen_bits:4096` to the `openssl genpkey` command above.
|
|
|
|
</Admonition>
|
|
|
|
## Step 2: Add environment variables
|
|
|
|
Add the following to your `.env` file:
|
|
|
|
```sh name=.env
|
|
############
|
|
# SAML SSO
|
|
############
|
|
|
|
SAML_ENABLED=true
|
|
SAML_PRIVATE_KEY=<your-base64-encoded-private-key>
|
|
|
|
# Optional: accept encrypted SAML assertions from IdPs (default: false)
|
|
# SAML_ALLOW_ENCRYPTED_ASSERTIONS=false
|
|
|
|
# Optional: how long relay state tokens remain valid (default: 2m0s)
|
|
# SAML_RELAY_STATE_VALIDITY_PERIOD=2m0s
|
|
|
|
# Optional: override the SAML entity ID / ACS base URL
|
|
# Defaults to API_EXTERNAL_URL if not set
|
|
# SAML_EXTERNAL_URL=https://supabase.example.com:8000
|
|
|
|
# Optional: rate limit on the ACS endpoint (requests per second, default: 15)
|
|
# SAML_RATE_LIMIT_ASSERTION=15
|
|
```
|
|
|
|
## Step 3: Pass SAML variables to the Auth container
|
|
|
|
In `docker-compose.yml`, add the SAML environment variables to the `auth` service. Auth expects the `GOTRUE_` prefix for all of its configuration variables:
|
|
|
|
```yaml name=docker-compose.yml
|
|
auth:
|
|
environment:
|
|
# ... existing variables ...
|
|
|
|
# SAML SSO
|
|
GOTRUE_SAML_ENABLED: ${SAML_ENABLED}
|
|
GOTRUE_SAML_PRIVATE_KEY: ${SAML_PRIVATE_KEY}
|
|
# GOTRUE_SAML_ALLOW_ENCRYPTED_ASSERTIONS: ${SAML_ALLOW_ENCRYPTED_ASSERTIONS}
|
|
# GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD: ${SAML_RELAY_STATE_VALIDITY_PERIOD}
|
|
# GOTRUE_SAML_EXTERNAL_URL: ${SAML_EXTERNAL_URL}
|
|
# GOTRUE_SAML_RATE_LIMIT_ASSERTION: ${SAML_RATE_LIMIT_ASSERTION}
|
|
```
|
|
|
|
## Step 4: Restart the containers
|
|
|
|
Apply the configuration changes:
|
|
|
|
```sh
|
|
docker compose down && \
|
|
docker compose up -d
|
|
```
|
|
|
|
Verify the Auth service is healthy:
|
|
|
|
```sh
|
|
docker compose ps auth
|
|
```
|
|
|
|
## Step 5: Retrieve your service provider metadata
|
|
|
|
Once SAML is enabled, your Supabase instance exposes service provider (SP) metadata at `{API_EXTERNAL_URL}/sso/saml/metadata`.
|
|
|
|
Verify it using `curl`:
|
|
|
|
```sh
|
|
curl http://<your-domain>/sso/saml/metadata
|
|
```
|
|
|
|
This returns an XML document containing your SP entity ID, ACS endpoint URL, and signing certificate. You will need to provide this to your IdP.
|
|
|
|
<Admonition type="tip">
|
|
|
|
{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
|
|
Add `?download=true` to the request URL to get the metadata as a downloadable XML file with a 5-year validity period - this is useful for IdPs that require a file upload instead of a URL.
|
|
|
|
</Admonition>
|
|
|
|
Key values in the metadata:
|
|
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
| Field | Value |
|
|
|---|---|
|
|
| Entity ID | `{API_EXTERNAL_URL}/sso/saml/metadata` |
|
|
| ACS URL | `{API_EXTERNAL_URL}/sso/saml/acs` |
|
|
| NameID formats | `persistent`, `emailAddress` |
|
|
| Signing certificate | Derived from your `SAML_PRIVATE_KEY` |
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
## Step 6: Register an identity provider
|
|
|
|
Use the Auth admin API to register your IdP. You need the `SERVICE_ROLE_KEY` for authentication.
|
|
|
|
### Option A: Register with a metadata URL (recommended)
|
|
|
|
If your IdP provides a metadata URL, Auth will fetch and cache the metadata automatically and refresh it when it becomes stale:
|
|
|
|
```sh
|
|
curl -X POST 'http://<your-domain>/auth/v1/admin/sso/providers' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'apikey: your-service-role-key' \
|
|
-d '{
|
|
"type": "saml",
|
|
"metadata_url": "https://idp.example.com/saml/metadata",
|
|
"domains": ["example.com"],
|
|
"attribute_mapping": {
|
|
"keys": {
|
|
"email": {
|
|
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
|
|
},
|
|
"name": {
|
|
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
|
|
}
|
|
}
|
|
}
|
|
}'
|
|
```
|
|
|
|
### Option B: Register with inline metadata XML
|
|
|
|
If you have the IdP metadata as an XML string:
|
|
|
|
```sh
|
|
curl -X POST 'http://<your-domain>/auth/v1/admin/sso/providers' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'apikey: your-service-role-key' \
|
|
-d '{
|
|
"type": "saml",
|
|
"metadata_xml": "<EntityDescriptor ...>...</EntityDescriptor>",
|
|
"domains": ["example.com"]
|
|
}'
|
|
```
|
|
|
|
<Admonition type="note">
|
|
|
|
{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
|
|
When using `metadata_url`, the URL must use HTTPS. Auth validates the metadata XML format and checks that the EntityID is unique across all registered providers.
|
|
|
|
</Admonition>
|
|
|
|
The response includes the provider `id` (UUID) - save this for use in your application or for later management:
|
|
|
|
```json
|
|
{
|
|
"id": "d3f5a1b2-...",
|
|
"resource_id": null,
|
|
"disabled": false,
|
|
"saml": {
|
|
"entity_id": "https://idp.example.com/saml",
|
|
"metadata_url": "https://idp.example.com/saml/metadata"
|
|
},
|
|
"domains": [{ "domain": "example.com" }],
|
|
"created_at": "...",
|
|
"updated_at": "..."
|
|
}
|
|
```
|
|
|
|
### Registration parameters reference
|
|
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
| Parameter | Required | Description |
|
|
|---|---|---|
|
|
| `type` | Yes | Must be `"saml"` |
|
|
| `metadata_url` | One of these | HTTPS URL to the IdP's SAML metadata (auto-refreshed) |
|
|
| `metadata_xml` | One of these | Raw IdP metadata XML string |
|
|
| `domains` | No | Array of email domains to associate (e.g., `["acme.com"]`). Used for domain-based SSO lookup. |
|
|
| `attribute_mapping` | No | Map SAML attributes to user claims (see [Attribute mapping](#attribute-mapping)) |
|
|
| `name_id_format` | No | Request a specific NameID format: `persistent`, `emailAddress`, `transient`, or `unspecified` |
|
|
| `resource_id` | No | A custom external identifier for the provider |
|
|
| `disabled` | No | Set to `true` to register but disable the provider |
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
## Step 8: Configure your identity provider
|
|
|
|
On the IdP side, create a new SAML application and configure it with your SP details:
|
|
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
| IdP setting | Value |
|
|
|---|---|
|
|
| SP Entity ID / Audience | `{API_EXTERNAL_URL}/sso/saml/metadata` |
|
|
| ACS URL / Reply URL | `{API_EXTERNAL_URL}/sso/saml/acs` |
|
|
| NameID format | `persistent` (recommended) or `emailAddress` |
|
|
| Signing certificate | Upload from the SP metadata XML or provide the metadata URL |
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
### IdP-specific configuration
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="okta"
|
|
>
|
|
|
|
<TabPanel id="okta" label="Okta">
|
|
|
|
**Okta setup:**
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
|
|
- Create a "SAML 2.0" application
|
|
- Single Sign-On URL: `{API_EXTERNAL_URL}/sso/saml/acs`
|
|
- Audience URI (SP Entity ID): `{API_EXTERNAL_URL}/sso/saml/metadata`
|
|
- Default RelayState: leave blank
|
|
- Name ID format: `Persistent`
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="azure" label="Azure">
|
|
|
|
**Azure AD (Entra ID):**
|
|
|
|
- Create an "Enterprise Application" → "Non-gallery application"
|
|
- Set up single sign-on → SAML
|
|
- Identifier (Entity ID): `{API_EXTERNAL_URL}/sso/saml/metadata`
|
|
- Reply URL (ACS): `{API_EXTERNAL_URL}/sso/saml/acs`
|
|
- Metadata URL for Auth: App Federation Metadata URL (from the SAML Signing Certificate section)
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="google" label="Google">
|
|
|
|
**Google Workspace:**
|
|
|
|
- Admin console → Apps → Web and mobile apps → Add custom SAML app
|
|
- ACS URL: `{API_EXTERNAL_URL}/sso/saml/acs`
|
|
- Entity ID: `{API_EXTERNAL_URL}/sso/saml/metadata`
|
|
- Name ID format: `PERSISTENT`
|
|
|
|
</TabPanel>
|
|
|
|
</Tabs>
|
|
|
|
## Attribute mapping
|
|
|
|
Attribute mapping lets you control how SAML assertion attributes are translated into Supabase user claims. If no mapping is provided, Auth uses sensible defaults:
|
|
|
|
**Default email detection order:**
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
|
|
1. `urn:oid:0.9.2342.19200300.100.1.3` (LDAP mail OID)
|
|
2. `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`
|
|
3. `http://schemas.xmlsoap.org/claims/EmailAddress`
|
|
4. Attributes named `mail`, `Mail`, or `email`
|
|
5. Subject NameID (if it looks like an email address)
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
**Default user ID detection:**
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
|
|
1. `urn:oasis:names:tc:SAML:attribute:subject-id` attribute
|
|
2. Subject NameID (if format is `persistent`)
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
### Custom attribute mapping example
|
|
|
|
Map IdP-specific attributes to user metadata:
|
|
|
|
```json
|
|
{
|
|
"attribute_mapping": {
|
|
"keys": {
|
|
"email": {
|
|
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
|
|
},
|
|
"name": {
|
|
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
|
|
},
|
|
"department": {
|
|
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/department",
|
|
"default": "unknown"
|
|
},
|
|
"groups": {
|
|
"name": "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups",
|
|
"array": true
|
|
},
|
|
"role": {
|
|
"names": ["http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "role", "Role"],
|
|
"default": "member"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Each mapping key supports:
|
|
|
|
| Field | Description |
|
|
| --------- | ---------------------------------------------------------------------------------------------------------- |
|
|
| `name` | Primary SAML attribute name to look for (matched against both `Name` and `FriendlyName`, case-insensitive) |
|
|
| `names` | Array of fallback attribute names to try in order |
|
|
| `default` | Default value if the attribute is not present in the assertion |
|
|
| `array` | Set to `true` to collect all values (for multi-valued attributes like groups) |
|
|
|
|
Mapped attributes are stored in the user's `raw_user_meta_data` and are available via `user.user_metadata` in your application.
|
|
|
|
## Managing providers
|
|
|
|
### List all providers
|
|
|
|
```sh
|
|
curl 'http://<your-domain>/auth/v1/admin/sso/providers' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'apikey: your-service-role-key'
|
|
```
|
|
|
|
Filter by resource ID using exact match:
|
|
|
|
```sh
|
|
curl 'http://<your-domain>/auth/v1/admin/sso/providers?resource_id=my-idp' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'apikey: your-service-role-key'
|
|
```
|
|
|
|
or prefix match:
|
|
|
|
```sh
|
|
curl 'http://<your-domain>/auth/v1/admin/sso/providers?resource_id_prefix=prod-' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'apikey: your-service-role-key'
|
|
```
|
|
|
|
### Get a specific provider
|
|
|
|
```sh
|
|
curl 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'apikey: your-service-role-key'
|
|
```
|
|
|
|
### Update a provider
|
|
|
|
```sh
|
|
curl -X PUT 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'apikey: your-service-role-key' \
|
|
-d '{
|
|
"domains": ["example.com", "subsidiary.com"],
|
|
"attribute_mapping": {
|
|
"keys": {
|
|
"email": {
|
|
"name": "mail"
|
|
}
|
|
}
|
|
}
|
|
}'
|
|
```
|
|
|
|
### Disable a provider (without deleting)
|
|
|
|
```sh
|
|
curl -X PUT 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'apikey: your-service-role-key' \
|
|
-d '{ "disabled": true }'
|
|
```
|
|
|
|
### Delete a provider
|
|
|
|
```sh
|
|
curl -X DELETE 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
|
|
-H 'Authorization: Bearer your-service-role-key' \
|
|
-H 'apikey: your-service-role-key'
|
|
```
|
|
|
|
## Client-side integration
|
|
|
|
### Using supabase-js
|
|
|
|
```js
|
|
import { createClient } from '@supabase/supabase-js'
|
|
|
|
const supabase = createClient('http://<your-domain>', 'your-anon-key')
|
|
|
|
// Option 1: SSO by email domain
|
|
const { data, error } = await supabase.auth.signInWithSSO({
|
|
domain: 'example.com',
|
|
})
|
|
|
|
// Option 2: SSO by provider ID
|
|
const { data, error } = await supabase.auth.signInWithSSO({
|
|
providerId: 'd3f5a1b2-...',
|
|
})
|
|
|
|
// Redirect the user to the IdP
|
|
if (data?.url) {
|
|
window.location.href = data.url
|
|
}
|
|
```
|
|
|
|
Both methods return an object with a `url` property - redirect the user to this URL to begin authentication at the IdP.
|
|
|
|
### Using the REST API directly
|
|
|
|
By domain:
|
|
|
|
```sh
|
|
curl -X POST 'http://<your-domain>/auth/v1/sso' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'apikey: your-anon-key' \
|
|
-d '{
|
|
"domain": "example.com",
|
|
"skip_http_redirect": true
|
|
}'
|
|
```
|
|
|
|
By provider ID:
|
|
|
|
```sh
|
|
curl -X POST 'http://<your-domain>/auth/v1/sso' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'apikey: your-anon-key' \
|
|
-d '{
|
|
"provider_id": "d3f5a1b2-...",
|
|
"skip_http_redirect": true
|
|
}'
|
|
```
|
|
|
|
Both return `{ "url": "https://idp.example.com/sso?SAMLRequest=..." }`.
|
|
|
|
### Domain-based vs provider-based lookup
|
|
|
|
| Method | Use case |
|
|
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `domain` | Extract the domain from the user's email and let Auth find the right IdP. Best for login forms where the user enters their email first. |
|
|
| `providerId` | Use when you know the exact provider - for example, a dedicated "Sign in with Okta" button. |
|
|
|
|
## Test the login flow
|
|
|
|
1. Open your application and trigger SSO login (or use the curl command above)
|
|
2. You should be redirected to your IdP's login page
|
|
3. After authenticating, the IdP posts back to the ACS endpoint
|
|
4. Auth processes the assertion and redirects you back to your `SITE_URL` (or `redirect_to` URL) with session tokens
|
|
|
|
To verify the session was created:
|
|
|
|
```sh
|
|
curl 'http://<your-domain>/auth/v1/user' \
|
|
-H 'Authorization: Bearer user-session-token' \
|
|
-H 'apikey: your-anon-key'
|
|
```
|
|
|
|
The response should include `app_metadata.provider: "sso:saml"` and any mapped attributes in `user_metadata`.
|
|
|
|
## Environment variable reference
|
|
|
|
{/* supa-mdx-lint-disable Rule003Spelling */}
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `SAML_ENABLED` | `false` | Enable the SAML SSO engine |
|
|
| `SAML_PRIVATE_KEY` | - | Base64-encoded PKCS#1 RSA private key (min 2048-bit). Used to sign SAML requests and optionally decrypt assertions. |
|
|
| `SAML_ALLOW_ENCRYPTED_ASSERTIONS` | `false` | Accept encrypted SAML assertions from IdPs |
|
|
| `SAML_RELAY_STATE_VALIDITY_PERIOD` | `2m0s` | How long relay state tokens remain valid. Increase if users on slow networks time out during the IdP redirect. |
|
|
| `SAML_EXTERNAL_URL` | `API_EXTERNAL_URL` | Override the base URL used for the SAML entity ID and ACS endpoint. Only needed if the SAML endpoints are served on a different URL than the rest of the Auth API. |
|
|
| `SAML_RATE_LIMIT_ASSERTION` | `15` | Maximum ACS requests per second. Protects against assertion replay floods. |
|
|
{/* supa-mdx-lint-enable Rule003Spelling */}
|
|
|
|
## Troubleshooting
|
|
|
|
### "SAML is not enabled on this server"
|
|
|
|
The `GOTRUE_SAML_ENABLED` variable is not set to `true`, or the Auth container did not pick up the change. Verify the env var is passed through `docker-compose.yml` and restart:
|
|
|
|
```sh
|
|
docker compose down && docker compose up -d
|
|
```
|
|
|
|
### "Invalid private key" on startup
|
|
|
|
The `GOTRUE_SAML_PRIVATE_KEY` value is malformed. Ensure it is:
|
|
|
|
- Base64-encoded (single line, no line breaks)
|
|
- In PKCS#1 format (`openssl pkey ... -traditional` output)
|
|
- At least 2048-bit RSA
|
|
|
|
Regenerate if needed:
|
|
|
|
```sh
|
|
openssl genpkey -algorithm RSA -out pk_pkcs8.pem -quiet && \
|
|
openssl pkey -in pk_pkcs8.pem -out pk_rsa1.der -outform DER -traditional && \
|
|
base64 -w 0 -i pk_rsa1.der
|
|
```
|
|
|
|
### IdP cannot reach the ACS endpoint
|
|
|
|
- Verify `API_EXTERNAL_URL` is set to a URL the IdP can reach (not `localhost` unless testing locally)
|
|
- Check that the API gateway routes for `/sso/saml/acs` and `/sso/saml/metadata` are configured as open (no `key-auth` plugin).
|
|
- Check the Auth container logs: `docker compose logs auth`
|
|
|
|
### "No SSO provider found for this domain"
|
|
|
|
- Verify the domain is registered: list providers and check the `domains` array
|
|
- Domain matching is exact and case-insensitive - `Example.com` matches `example.com`
|
|
|
|
### Assertion validation fails
|
|
|
|
- Ensure the IdP's signing certificate matches what is in the metadata registered with Auth
|
|
- If using `metadata_url`, Auth automatically refreshes stale metadata (after `ValidUntil`, `CacheDuration`, or 24 hours). Force a refresh by updating the provider.
|
|
- Check clock sync between your server and the IdP - SAML assertions have time-based validity windows (`NotBefore` / `NotOnOrAfter`)
|
|
|
|
### User is created but attributes are missing
|
|
|
|
{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
|
|
|
|
- Check your `attribute_mapping` configuration. Use the IdP's SAML assertion viewer (most IdPs have one) to see the exact attribute names being sent.
|
|
- Attribute names are matched case-insensitively against both the `Name` and `FriendlyName` fields in the assertion.
|
|
- Mapped attributes appear in `user.user_metadata`.
|
|
|
|
### Relay state expired
|
|
|
|
The user took too long between initiating SSO and completing authentication at the IdP. Increase `GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD` (default is 2 minutes).
|
|
|
|
## Additional resources
|
|
|
|
- [SSO with SAML 2.0](/docs/guides/auth/enterprise-sso/auth-sso-saml) - Client-side SAML integration guide
|
|
- [Auth server configuration reference](/docs/guides/self-hosting/auth/config) - Full list of Auth environment variables
|
|
- [SAML 2.0 specification](http://docs.oasis-open.org/security/saml/v2.0/) - The underlying standard
|