# Configuring Entra ID SAML SSO with Self-Hosted Supabase

##

This guide walks through configuring Microsoft Entra ID (formerly Azure AD) as a SAML identity provider for your self-hosted Supabase instance to enable SSO authentication with sfp pro CLI/codev

### Prerequisites

Before starting the configuration, ensure you have:

* **Self-Hosted Supabase Requirements:**
  * Running Supabase instance (Docker or Kubernetes)
  * Access to modify environment variables and configuration files
  * SSL certificates configured (SAML requires HTTPS)
  * Ability to restart services
* **Entra ID Requirements:**
  * Admin access to your Microsoft Entra ID tenant
  * At least one verified domain in your Entra ID tenant
  * Ability to create Enterprise Applications
* **sfp Requirements:**
  * sfp CLI installed
  * Access to your sfp server instance
  * sfp server URL (e.g., `https://your-sfp-server.com`)

### Step 1: Enable SAML in Self-Hosted Supabase

#### Generate Private Key

SAML requires a private key for signing. Generate one:

bash

```bash
# Generate private key in DER format
openssl genpkey -algorithm rsa -outform DER -out private_key.der

# Convert to base64
base64 -i private_key.der
```

#### Configure Environment Variables

Add to your `.env` file (in the same directory as `docker-compose.yml`):

bash

```bash
# Enable SAML authentication
GOTRUE_SAML_ENABLED=true
GOTRUE_SAML_PRIVATE_KEY=<your-base64-private-key>

# Ensure your external URL is set correctly
API_EXTERNAL_URL=https://your-supabase-instance.com
GOTRUE_EXTERNAL_URL=https://your-supabase-instance.com
```

#### Update Docker Compose Configuration

In the tenant's `docker-compose.yml`, find the `supabase-auth` service and add the SAML environment variables:

```yaml
  supabase-auth:
    environment:
      # ... existing environment variables ...
      GOTRUE_SAML_ENABLED: ${GOTRUE_SAML_ENABLED:-false}
      GOTRUE_SAML_PRIVATE_KEY: ${GOTRUE_SAML_PRIVATE_KEY:-}
```

> If deployed via `sfp server init`, the compose file is at `/opt/sfp-server/tenants/<your-tenant>/docker-compose.yml`. Add the two lines above to the `supabase-auth` service's `environment` block. The values are read from `.env` automatically.

#### Configure Kong API Gateway

Edit `/docker/volumes/api/kong.yml` to expose SAML endpoints:

yaml

```yaml
## Open SSO routes for SAML
- 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
```

#### Configure Reverse Proxy

The SAML ACS endpoint uses `/sso/*` paths that must be routed to the GoTrue auth service directly (not through Kong). Add the following to your reverse proxy:

**Caddy** (used by `sfp server init`):

```
handle /sso/* {
    reverse_proxy http://supabase-auth:9999
}
```

Add this block to the Caddyfile in your tenant's `config/` directory, before the catch-all `handle` block.

**Nginx** (if using Nginx instead of Caddy):

```nginx
location ~ ^/sso/(.*)$ {
    proxy_set_header Host $host;
    proxy_pass http://supabase-auth:9999;
    proxy_redirect off;
}
```

> The `/sso/*` paths must route directly to GoTrue (port 9999), not through Kong. Kong's declarative config does not include SSO routes by default.

#### Restart Services

```bash
# Restart the auth container to pick up SAML config
cd /opt/sfp-server/tenants/<your-tenant>
docker compose --profile supabase up -d supabase-auth
```

> If deployed via `sfp server init`, the tenant directory is at `/opt/sfp-server/tenants/<your-tenant>/`. Use `--profile supabase` for self-hosted deployments.

#### Verify SAML is Enabled

bash

```bash
# Get your service role key from .env or docker-compose.yml
API_KEY=<your-service-role-key>

# Check settings
curl -X GET https://your-supabase-instance.com/auth/v1/settings \
  -H "APIKey: $API_KEY" \
  -H "Authorization: Bearer $API_KEY"
```

You should see `"saml_enabled": true` in the response.

### Step 2: Note Your Supabase Configuration

Gather your self-hosted instance information:

bash

```bash
# Your Supabase instance URL
SUPABASE_URL=https://your-supabase-instance.com

# Your Supabase anon key (from .env or docker-compose.yml)
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# SAML endpoints (note: no /auth/v1/ prefix)
Entity ID: https://your-supabase-instance.com/sso/saml/metadata
ACS URL: https://your-supabase-instance.com/sso/saml/acs
Metadata URL: https://your-supabase-instance.com/auth/v1/sso/saml/metadata
```

### Step 3: Configure Entra ID Enterprise Application

#### Create the Application

1. Sign in to [Azure Portal](https://portal.azure.com)
2. Navigate to **Microsoft Entra ID** → **Enterprise applications**
3. Click **New application** → **Create your own application**
4. Select **Integrate any other application you don't find in the gallery (Non-gallery)**
5. Name it (e.g., "sfp SSO Self-Hosted")
6. Click **Create**

#### Configure SAML Settings

1. In your application, go to **Single sign-on**
2. Select **SAML** as the method
3. Click **Edit** in Basic SAML Configuration
4. Configure these URLs (replace `flxbl.yourcompany.com` with your actual domain):

| Field                                          | Value                                             |
| ---------------------------------------------- | ------------------------------------------------- |
| **Identifier (Entity ID)**                     | `https://flxbl.yourcompany.com/sso/saml/metadata` |
| **Reply URL (Assertion Consumer Service URL)** | `https://flxbl.yourcompany.com/sso/saml/acs`      |
| **Sign on URL**                                | `https://flxbl.yourcompany.com`                   |
| **Logout URL**                                 | `https://flxbl.yourcompany.com/sso/saml/slo`      |

> These URLs do **not** include `/auth/v1/` — GoTrue uses the `/sso/` path directly. You can verify the exact values by fetching the SAML metadata from your instance: `curl https://flxbl.yourcompany.com/auth/v1/sso/saml/metadata`

5. Click **Save**

#### Configure Attributes and Claims

1. In **Attributes & Claims**, click **Edit**
2. Ensure these claims are configured:

| Claim                                                                | Source attribute   |
| -------------------------------------------------------------------- | ------------------ |
| `emailaddress` (Name ID)                                             | `user.mail`        |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `user.mail`        |
| `http://schemas.microsoft.com/identity/claims/displayname`           | `user.displayname` |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname`    | `user.givenname`   |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname`      | `user.surname`     |

> The Name ID format should be set to **Email address** (`emailAddress`).

#### Download Federation Metadata

1. In **SAML Certificates** section
2. Download **Federation Metadata XML**
3. Note the **SAML Metadata URL** if available

#### Assign Users

1. Go to **Users and groups**
2. Click **Add user/group**
3. Select users who need sfp access
4. Click **Assign**

### Step 4: Add SAML Provider to Self-Hosted Supabase

Since self-hosted Supabase doesn't have CLI support for SSO management, use the Admin API:

#### Add the Identity Provider

bash

```bash
# Set your service role key
API_KEY=<your-service-role-key>

# Add SAML provider using metadata URL
curl -X POST https://your-supabase-instance.com/auth/v1/admin/sso/providers \
  -H "APIKey: $API_KEY" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "saml",
    "metadata_url": "https://login.microsoftonline.com/<tenant-id>/federationmetadata/2007-06/federationmetadata.xml",
    "domains": ["yourdomain.com"]
  }'
```

Or using metadata XML content:

bash

```bash
# Read metadata file content
METADATA_XML=$(cat /path/to/federation-metadata.xml | jq -sR .)

# Add provider with metadata XML
curl -X POST https://your-supabase-instance.com/auth/v1/admin/sso/providers \
  -H "APIKey: $API_KEY" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"type\": \"saml\",
    \"metadata_xml\": $METADATA_XML,
    \"domains\": [\"yourdomain.com\"]
  }"
```

#### Configure Attribute Mappings

Get the provider ID from the previous response, then update mappings:

bash

```bash
PROVIDER_ID=<provider-id-from-response>

curl -X PUT "https://your-supabase-instance.com/auth/v1/admin/sso/providers/$PROVIDER_ID" \
  -H "APIKey: $API_KEY" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "attribute_mapping": {
      "keys": {
        "email": {
          "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
        },
        "full_name": {
          "name": "http://schemas.microsoft.com/identity/claims/displayname"
        },
        "first_name": {
          "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
        },
        "last_name": {
          "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
        }
      }
    }
  }'
```

#### Verify Provider Configuration

bash

```bash
# List all providers
curl -X GET https://your-supabase-instance.com/auth/v1/admin/sso/providers \
  -H "APIKey: $API_KEY" \
  -H "Authorization: Bearer $API_KEY"
```

### Step 5: Add Users to the Server

> SAML authentication alone does not grant access. Users must also have a **team membership** on the server before they can sign in.

After a user authenticates via SAML for the first time, they will see "No Access — No team memberships found" (AUTH-002). An administrator must add them to the server's team:

1. Sign in to the application at `https://flxbl.yourcompany.com` with the admin account created during `sfp server init`
2. Navigate to **Settings → Users** and invite the user by email
3. The user's email must match their Entra ID email (e.g., `user@yourdomain.com`)

> If the admin credentials were lost, they are saved in `sfp-server-init-{tenant}.json` on the machine that ran the init command. If that file is also unavailable, the password can be reset via the Supabase Admin API:
>
> ```bash
> # On the server:
> SERVICE_KEY=$(grep SUPABASE_SERVICE_KEY .env | cut -d= -f2)
> ANON_KEY=$(grep SUPABASE_ANON_KEY .env | cut -d= -f2)
> curl -X PUT "http://localhost:8000/auth/v1/admin/users/<admin-user-id>" \
>   -H "Authorization: Bearer $SERVICE_KEY" \
>   -H "apikey: $ANON_KEY" \
>   -H "Content-Type: application/json" \
>   -d '{"password":"new-password-here"}'
> ```

Alternatively, use the sfp CLI to add users without the web UI:

```bash
sfp server user add \
  --firstname "Jane" \
  --lastname "Doe" \
  --target-email "jane.doe@yourdomain.com" \
  --team "my-company" \
  --role owner \
  --sfp-server-url https://flxbl.yourcompany.com \
  --email admin@my-company.local
```

> **Why two steps?** SAML handles *authentication* (verifying identity via your IdP). The server handles *authorization* (checking team membership). This separation allows multiple IdPs to authenticate users while the server controls who has access to which tenant.

#### Entra ID User Assignment (Optional)

By default, all users in your Entra ID tenant can use the SAML application. To restrict access to specific users:

1. In the Azure Portal, go to **Enterprise applications → FLXBL Self-Hosted → Properties**
2. Set **Assignment required?** to **Yes**
3. Go to **Users and groups → Add user/group** and assign only the users who should have access

> When "Assignment required" is set to No (the default), any user in your Entra ID tenant can authenticate. The server's team membership check provides the second layer of access control.

### Step 6: Configure sfp CLI (Optional)

Configure sfp to use your self-hosted Supabase instance:

bash

```bash
# Set your self-hosted Supabase URL
sfp config:set auth-supabase-url https://your-supabase-instance.com

# Set your Supabase anon key
sfp config:set auth-supabase-anon-key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# Optionally, set default SSO domain
sfp config:set auth-sso-domain yourdomain.com
```

Example:

bash

```bash
sfp config:set auth-supabase-url https://supabase.mycompany.com
sfp config:set auth-supabase-anon-key "your-anon-key-from-env"
sfp config:set auth-sso-domain mycompany.com
```

### Step 7: Test SAML Authentication

#### Test SSO URL Generation

First, verify the SSO URL can be generated:

bash

```bash
API_KEY=<your-service-role-key>

curl -X POST https://your-supabase-instance.com/auth/v1/sso \
  -H "APIKey: $API_KEY" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "yourdomain.com",
    "skip_http_redirect": true
  }'
```

You should receive a JSON response with a URL to the Entra ID login page.

#### Test with sfp CLI

bash

```bash
sfp server auth login \
  --email your-email@yourdomain.com \
  --provider saml \
  --sso-domain yourdomain.com \
  --sfp-server-url https://your-sfp-server.com \
  --no-global-auth
```

Example:

bash

```bash
sfp server auth login \
  --email user@mycompany.com \
  --provider saml \
  --sso-domain mycompany.com \
  --sfp-server-url https://sfp.mycompany.com \
  --no-global-auth
```

### Troubleshooting

#### Common Issues

**"saml\_enabled": false in settings**

* Check environment variables are set correctly
* Verify docker-compose.yml passes SAML variables to auth container
* Restart all containers: `docker-compose down && docker-compose up -d`

**Kong routes not working**

* Verify kong.yml has been updated with SSO routes
* Check Kong logs: `docker logs supabase-kong`
* Ensure paths are correctly configured

**SSL/HTTPS issues**

* SAML requires HTTPS - ensure SSL is properly configured
* Check reverse proxy configuration if using one
* Verify API\_EXTERNAL\_URL and GOTRUE\_EXTERNAL\_URL use https\://

**"No SSO provider assigned for this domain"**

* Check provider was added successfully
* Verify domain matches exactly
* List providers to confirm configuration

**Metadata URL not accessible**

* For on-premise Entra ID behind VPN, use metadata XML instead
* Ensure your Supabase instance can reach Microsoft's URLs

#### Debug Commands

bash

```bash
# Check Docker logs
docker logs supabase-auth
docker logs supabase-kong

# Verify SAML endpoints are accessible
curl https://your-supabase-instance.com/auth/v1/sso/saml/metadata

# Check sfp configuration
sfp config:list

# Test with debug logging
sfp server auth login \
  --email user@yourdomain.com \
  --provider saml \
  --sso-domain yourdomain.com \
  --sfp-server-url https://your-sfp-server.com \
  --no-global-auth \
  --loglevel debug
```

### Security Considerations

#### SSL/TLS Configuration

Ensure proper SSL configuration for SAML:

nginx

```nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
```

#### Network Security

1. **Firewall Rules**: Allow HTTPS traffic from Entra ID services
2. **Internal Communication**: Secure communication between containers
3. **Secret Management**: Store sensitive keys securely

#### Monitoring

1. Set up log aggregation for auth services
2. Monitor failed authentication attempts
3. Track certificate expiration dates
4. Regular security audits

### Managing Multiple Environments

For different environments with self-hosted instances:

bash

```bash
# Development
sfp config:set auth-supabase-url https://dev.supabase.mycompany.com
sfp config:set auth-supabase-anon-key "dev-anon-key"

# Production
sfp config:set auth-supabase-url https://supabase.mycompany.com
sfp config:set auth-supabase-anon-key "prod-anon-key"
```

Or use environment variables:

bash

```bash
export SUPABASE_URL=https://dev.supabase.mycompany.com
export SUPABASE_ANON_KEY=dev-anon-key
export AUTH_SSO_DOMAIN=dev.mycompany.com
```

### Next Steps

After successful configuration:

1. Document the setup for your operations team
2. Create runbooks for common issues
3. Set up monitoring and alerting
4. Plan for certificate rotation
5. Consider implementing SCIM for user provisioning
6. Document disaster recovery procedures
