PKCE (Proof Key for Code Exchange) definition and usage
Proof Key for Code Exchange is an extension to the OAuth protocol that enables secure exchange of refresh and access tokens between an application (web app, single-page app, or mobile app) and the authorization server. It is used in places where the exchange of the refresh and access token may be intercepted by third parties such as other applications running in the operating system. This is a common problem on mobile devices where the operating system may hand out URLs to other applications. This can sometimes also be exploited in single-page apps.
Provider refresh token definition
A provider refresh token is a refresh token issued by a third-party identity provider which can be used to refresh the provider token returned.
Refresh token definition and behavior
A refresh token is a long-lived token, in most cases with an indefinite lifetime, that is meant to be stored and exchanged for a new refresh and access tokens only once. Once a refresh token is exchanged it becomes invalid, and cannot be exchanged again. In practice, though, a refresh token can be exchanged multiple times but in a short time window.
Refresh token flow mechanism
The refresh token flow is a mechanism that issues a new refresh and access token on the basis of a valid refresh token. It is used to extend authorization access for an application. An application that is being constantly used will invoke the refresh token flow before the access token expires.
Replay attack definition and mitigation
A replay attack is when sensitive information is stolen or intercepted by attackers who then attempt to use it again (thus replay) in an effort to compromise a system. Commonly, replay attacks can be mitigated with the proper use of nonces.
SAML (Security Assertion Markup Language) protocol
SAML stands for Security Assertion Markup Language and is a protocol that enables single-sign on for enterprises. SAML was invented in the early 2000s and is based on XML technology. It is the de facto standard for enabling single-sign on for enterprises, although the more recent OIDC (OpenID Connect) protocol is gaining popularity.
Session and authentication session definition
A session or authentication session is the concept that binds a verified user identity to a web browser. A session usually is long-lived, and can be terminated by the user logging out. An access and refresh token pair represent a session in the browser, and they are stored in local storage or as cookies.
Single-sign on (SSO) definition and purpose
Single-sign on allows enterprises to centrally manage accounts and access to applications. They use identity provider software or services to organize employee information in directories and connect those accounts with applications via OIDC or SAML protocols.
Time-based one-time password (TOTP) definition
A time-based one-time password is a one-time password generated at regular time intervals from a secret, usually from an application in a mobile device (e.g., Google Authenticator, 1Password).
Auth security guides index
Supabase provides security guides for auth covering: password security, rate limits, bot detection and prevention with CAPTCHA, and JWTs.
Auth Audit Logs
Supabase Auth provides Auth Audit Logs to monitor authentication-related activities within projects, separate from Platform Audit Logs.
SSO for Supabase organizations
Supabase offers single sign-on (SSO) as a login option to provide additional account security for teams. This allows company administrators to enforce the use of an identity provider when logging into Supabase.
Notification email template environment variables
Notification email templates are configured using three types of environment variables: GOTRUE_MAILER_NOTIFICATIONS_<NOTIFICATION_TYPE>_ENABLED to enable the notification email, GOTRUE_MAILER_TEMPLATES_<NOTIFICATION_TYPE>_NOTIFICATION to provide a custom template URL, and GOTRUE_MAILER_SUBJECTS_<NOTIFICATION_TYPE>_NOTIFICATION to customize the email subject. The notification types are: PASSWORD_CHANGED (when a user's password is changed), EMAIL_CHANGED (when a user's email is changed), PHONE_CHANGED (when a user's phone is changed), MFA_FACTOR_ENROLLED (when a new MFA factor is added), MFA_FACTOR_UNENROLLED (when an MFA factor is removed), IDENTITY_LINKED (when a new identity is linked), and IDENTITY_UNLINKED (when an identity is unlinked).
Authentication email template environment variables
Authentication email templates are configured using two types of environment variables: GOTRUE_MAILER_TEMPLATES_<AUTH_FLOW> to provide a custom template URL, and GOTRUE_MAILER_SUBJECTS_<AUTH_FLOW> to customize the email subject. Both fall back to defaults if not set. The auth flow values are: CONFIRMATION (when a user signs up and needs to verify their email), RECOVERY (when a user requests a password reset), MAGIC_LINK (when a user requests a magic link for passwordless authentication), INVITE (when a user is invited via email invitation), EMAIL_CHANGE (when a user requests to change their email), and REAUTHENTICATION (when a user needs to re-authenticate for sensitive operations).
Phone MFA disabled by default
Phone MFA is disabled by default (opt-in) in self-hosted Supabase. To enable it, set MFA_PHONE_ENROLL_ENABLED=true and MFA_PHONE_VERIFY_ENABLED=true in .env. Phone MFA uses the same SMS provider configuration as phone login.
TOTP MFA enabled by default
TOTP (app authenticator) multi-factor authentication is enabled by default in self-hosted Supabase. Users can enroll with apps like Google Authenticator or Authy without additional configuration. To disable, set MFA_TOTP_ENROLL_ENABLED=false and MFA_TOTP_VERIFY_ENABLED=false in .env.
SMS test OTP expiration configuration
Set SMS_TEST_OTP_VALID_UNTIL in .env to an ISO 8601 datetime (e.g., 2026-12-31T23:59:59Z) to automatically expire test OTPs without manual removal.
Test OTP mapping for development
Use SMS_TEST_OTP in .env to map phone numbers to fixed OTP codes during development, avoiding real SMS delivery. Format: SMS_TEST_OTP=16505551234:123456,16505555678:654321. Ensure GOTRUE_SMS_TEST_OTP: ${SMS_TEST_OTP} is uncommented in docker-compose.yml. When a test phone number requests an OTP, the Auth service skips delivery and accepts only the mapped code. Remove test OTPs before production deployment.
SMS rate limiting configuration
SMS_MAX_FREQUENCY controls the minimum interval between SMS sends to the same phone number. The default is 60 seconds. Set it to a value like '30s' to allow one SMS every 30 seconds.
OTP length configuration range
The OTP length can be set to any value between 6 and 10 digits using SMS_OTP_LENGTH in .env. The default is 6 digits.
Default OTP expiration is 60 seconds
The default OTP expiration time is 60 seconds, which is often too short for production use. Increase SMS_OTP_EXP in .env to a higher value (in seconds, e.g., 300 for 5 minutes).
GOTRUE_SMS_* environment variables in docker-compose.yml
The auth service's environment block in docker-compose.yml must include these GOTRUE_* passthrough definitions to make SMS configuration available to the container: GOTRUE_SMS_PROVIDER, GOTRUE_SMS_OTP_EXP, GOTRUE_SMS_OTP_LENGTH, GOTRUE_SMS_MAX_FREQUENCY, GOTRUE_SMS_TEMPLATE, and provider-specific lines like GOTRUE_SMS_TWILIO_ACCOUNT_SID, GOTRUE_SMS_TWILIO_AUTH_TOKEN, GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID. Variables from .env are not automatically available in the container without these passthrough definitions.
SMS provider environment variables for self-hosted auth
To configure SMS providers in self-hosted Supabase, add these variables to .env: SMS_PROVIDER (e.g., 'twilio'), SMS_OTP_EXP (seconds, default 60), SMS_OTP_LENGTH (digits, default 6), SMS_MAX_FREQUENCY (interval between sends, default '60s'), SMS_TEMPLATE (template string with {{ .Code }} placeholder), and provider-specific credentials like SMS_TWILIO_ACCOUNT_SID, SMS_TWILIO_AUTH_TOKEN, SMS_TWILIO_MESSAGE_SERVICE_SID.
Phone login enabled by default in Docker
Phone authentication is enabled by default in self-hosted Docker setup with ENABLE_PHONE_SIGNUP=true in the .env file. However, without an SMS provider configured, the Auth service cannot deliver OTP codes.
Global SMS rate limit default
The global rate limit for SMS sends is GOTRUE_RATE_LIMIT_SMS_SENT with a default of 30 per hour. If users see rate limit exceeded errors, check both SMS_MAX_FREQUENCY (interval between sends to same number) and this global limit.
Restart auth service after configuration changes
After modifying SMS or MFA environment variables, restart the auth service with: sh run.sh recreate auth
Verify SMS configuration in running container
After configuring SMS variables, verify they reach the auth container by running: sh run.sh printenv auth | grep GOTRUE_SMS. Configuration variables from .env are not automatically available in the container unless they have matching passthrough definitions in docker-compose.yml.
E.164 phone number format required
Phone numbers must be formatted in E.164 format (e.g., +1234567890) for SMS delivery. Incorrect phone number formatting is a common cause of SMS delivery failures.
Maximum enrolled MFA factors per user
By default, a user can enroll up to 10 MFA factors. Change this by setting MFA_MAX_ENROLLED_FACTORS in .env to a different value (e.g., 5).
Security notification email templates available
The following security notification email templates can be configured: auth.email.notification.password_changed (default subject: 'Your password was changed'), auth.email.notification.email_changed (default subject: 'Your email address was changed'), auth.email.notification.phone_changed (default subject: 'Your phone number was changed'), auth.email.notification.mfa_factor_enrolled (default subject: 'A new verification method was added to your account'), auth.email.notification.mfa_factor_unenrolled (default subject: 'A verification method was removed from your account'), auth.email.notification.identity_linked (default subject: 'A sign-in method was linked to your account'), auth.email.notification.identity_unlinked (default subject: 'A sign-in method was removed from your account'). These are only sent if security notifications have been enabled at the project-level.
Authentication email templates available
The following authentication email templates can be configured: auth.email.template.invite (default subject: 'You've been invited', sent when user is invited via email), auth.email.template.confirmation (default subject: 'Confirm your email address', sent when user signs up), auth.email.template.recovery (default subject: 'Reset your password', sent for password reset requests), auth.email.template.magic_link (default subject: 'Your sign-in link', sent for passwordless sign-in), auth.email.template.email_change (default subject: 'Confirm your new email address', sent when user changes email), auth.email.template.reauthentication (default subject: '{{ .Token }} is your verification code', sent for sensitive operations requiring re-authentication).
Local email template configuration in config.toml
Email templates for local development are configured in the config.toml file using the content_path parameter to point to an HTML file. Authentication email templates are configured under [auth.email.template.*] sections (e.g., [auth.email.template.invite]), while security notification templates are configured under [auth.email.notification.*] sections (e.g., [auth.email.notification.password_changed]). Each template configuration requires a subject line and content_path pointing to a relative URL of the HTML file.
Email templates for hosted Supabase projects
For hosted projects managed by Supabase, email templates are configured using the Email Templates page in the dashboard at /dashboard/project/_/auth/templates, not through config.toml. For self-hosted Supabase instances, custom email templates are configured differently as documented in the Custom Email Templates guide.
Email template TOML configuration example
Example of authentication email template configuration:
```toml
[auth.email.template.invite]
subject = "You are invited to Acme Inc"
content_path = "./supabase/templates/invite.html"
```
Example of security notification template configuration:
```toml
[auth.email.notification.password_changed]
enabled = true
subject = "Your password was changed"
content_path = "./templates/password_changed_notification.html"
```
Email template HTML example with variables
Example HTML email template:
```html
<html>
<body>
<h2>Confirm your email address</h2>
<p>Follow the link below to confirm this email address and finish signing up.</p>
<p><a href="{{ .ConfirmationURL }}">Confirm email address</a></p>
</body>
</html>
```
Another example using TokenHash and SiteURL:
```html
<p>Follow the link below to confirm this email address and finish signing up.</p>
<p>
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email"
>Confirm email address</a
>
</p>
```
Email template variables reference
Email templates support the following variables: ConfirmationURL (the confirmation URL for email verification), Token (8-digit OTP code), TokenHash (hashed version of Token for constructing custom links), SiteURL (application's Site URL from auth settings), RedirectTo (redirect URL passed as redirectTo option), Data (metadata from auth.users.user_metadata), Email (user's email address), NewEmail (new email address, only in email_change template), OldEmail (old email address, only in email_changed_notification template), Phone (new phone number, only in phone_changed_notification template), OldPhone (old phone number, only in phone_changed_notification template), Provider (sign-in method provider, only in identity_linked/unlinked_notification templates), FactorType (verification method type, only in mfa_factor_enrolled/unenrolled_notification templates). Variables are used with the syntax {{ .VariableName }}.
Pull auth schema migrations locally
If you have additional triggers or RLS policies defined on your auth schema, you can pull them as a migration file locally by running 'supabase db pull --schema auth'.
Configure Auth providers locally in config.toml
To use Auth locally, update the supabase/config.toml file created after running 'supabase init'. Add any providers you want with enabled = true. Example for GitHub: [auth.external.github] enabled = true, client_id = "env(SUPABASE_AUTH_GITHUB_CLIENT_ID)", secret = "env(SUPABASE_AUTH_GITHUB_SECRET)", redirect_uri = "http://localhost:54321/auth/v1/callback". Secret values should be loaded from environment variables in an .env file.
Passing SAML variables to Auth container in docker-compose
In docker-compose.yml, add SAML environment variables to the auth service with the GOTRUE_ prefix: 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_RATE_LIMIT_ASSERTION: ${SAML_RATE_LIMIT_ASSERTION}.
SAML environment variables for self-hosted Supabase
Add the following to .env: SAML_ENABLED=true; SAML_PRIVATE_KEY=<your-base64-encoded-private-key>. Optional variables: SAML_ALLOW_ENCRYPTED_ASSERTIONS (default: false, accepts encrypted SAML assertions); SAML_RELAY_STATE_VALIDITY_PERIOD (default: 2m0s, how long relay state tokens remain valid); SAML_RATE_LIMIT_ASSERTION (default: 15, ACS endpoint requests per second).
API_EXTERNAL_URL requirement for self-hosted SAML
API_EXTERNAL_URL must be set to the publicly-accessible URL of the Supabase Auth service (e.g., https://<your-domain>/auth/v1). This URL is used as the base for constructing the SAML Service Provider entity ID and ACS endpoint URL.
SAML login flow in Supabase
The SAML SSO login flow works as follows: (1) the application 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 /auth/v1/sso/saml/acs, (5) Auth validates the assertion, creates or links the user, and issues a session, (6) the user is redirected back to the app with session tokens.
SAML SSO configuration layers in self-hosted Supabase
SAML SSO in self-hosted Supabase is configured in two layers. First, global SAML enable uses environment variables (SAML_ENABLED and SAML_PRIVATE_KEY) that go in .env and docker-compose.yml files. Second, per-IdP configuration uses the Auth admin API to register, update, and delete individual Identity Providers at runtime without requiring a restart.
SAML private key generation for self-hosted Supabase
Generate an RSA private key with: 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 key must be Base64-encoded PKCS#1 DER format with a minimum 2048-bit key size. For production, use 4096-bit by adding -pkeyopt rsa_keygen_bits:4096 to the openssl genpkey command. Save the Base64 output as a single line, ignoring trailing newlines.
SAML troubleshooting: Relay state expired
If user takes too long between initiating SSO and completing authentication at IdP, relay state expires. Increase GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD (default is 2 minutes).
SAML troubleshooting: User created but attributes missing
If user is created but attributes are missing: check attribute_mapping configuration, use IdP's SAML assertion viewer (most IdPs have one) to see exact attribute names being sent, attribute names are matched case-insensitively against both Name and FriendlyName fields in assertion, mapped attributes appear in user.user_metadata.
SAML troubleshooting: Assertion validation fails
If assertion validation fails: ensure 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 refresh by updating provider), check clock sync between server and IdP (SAML assertions have time-based validity windows NotBefore/NotOnOrAfter).
SAML troubleshooting: No SSO provider found for domain
If error 'No SSO provider found for this domain' appears: verify the domain is registered by listing providers and checking the domains array, domain matching is exact and case-insensitive (Example.com matches example.com).
SAML troubleshooting: IdP cannot reach ACS endpoint
If IdP cannot reach ACS endpoint: verify API_EXTERNAL_URL is set to a URL containing /auth/v1 that the IdP can reach (not localhost unless testing locally), check that API gateway routes for /auth/v1/sso/saml/acs and /auth/v1/sso/saml/metadata are open (no key-auth plugin), check Auth container logs with docker compose logs auth.
SAML troubleshooting: Invalid private key on startup
If 'Invalid private key' error occurs on startup: ensure GOTRUE_SAML_PRIVATE_KEY is Base64-encoded (single line, no line breaks), in PKCS#1 format (from openssl pkey ... -traditional), and at least 2048-bit RSA. Regenerate with: 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.
SAML troubleshooting: SAML not enabled error
If error 'SAML is not enabled on this server' appears: GOTRUE_SAML_ENABLED variable is not set to true or Auth container did not pick up the change. Verify env var is passed through docker-compose.yml and restart with sh run.sh recreate.
SAML environment variables reference for self-hosted Supabase
SAML environment variables: SAML_ENABLED (default: false, enable SAML SSO engine), SAML_PRIVATE_KEY (no default, Base64-encoded PKCS#1 RSA private key min 2048-bit, used to sign requests and optionally decrypt assertions), SAML_ALLOW_ENCRYPTED_ASSERTIONS (default: false, accept encrypted SAML assertions), SAML_RELAY_STATE_VALIDITY_PERIOD (default: 2m0s, how long relay state tokens remain valid), SAML_RATE_LIMIT_ASSERTION (default: 15, max ACS requests per second).
Testing SAML login flow in self-hosted Supabase
To test SAML login: (1) open application and trigger SSO login or use curl command, (2) verify redirection to IdP's login page, (3) authenticate at IdP, (4) verify IdP POSTs to ACS endpoint, (5) verify Auth redirects back to SITE_URL or redirect_to URL with session tokens. To verify session creation: curl 'http://<your-domain>/auth/v1/user' -H 'Authorization: Bearer user-session-token' -H 'apikey: your-anon-key'. Response should include app_metadata.provider: "sso:saml" and any mapped attributes in user_metadata.
Domain-based vs provider-based SAML SSO lookup
Domain-based lookup: extract domain from user's email and let Auth find the right IdP, best for login forms where user enters email first. Provider-based lookup: use when you know the exact provider, for example a dedicated 'Sign in with Okta' button.
SAML SSO client-side integration with supabase-js
Client-side SAML SSO using supabase-js: import { createClient } from '@supabase/supabase-js'. Create client with createClient('http://<your-domain>', 'your-anon-key'). Sign in by email domain: await supabase.auth.signInWithSSO({domain: 'example.com'}). Sign in by provider ID: await supabase.auth.signInWithSSO({providerId: 'd3f5a1b2-...'}). Both return {data, error} with data.url as the IdP redirect URL.
Update SAML provider via admin API
Update a provider: 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"}}}}'.
Get specific SAML provider via admin API
Get a specific provider: curl 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' -H 'Authorization: Bearer your-service-role-key' -H 'apikey: your-service-role-key'.
List all SAML providers via admin API
List all SAML providers: 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: add ?resource_id=my-idp. Filter by resource ID prefix: add ?resource_id_prefix=prod-.
Mapped SAML attributes storage in user metadata
Mapped SAML attributes are stored in the user's raw_user_meta_data and are available via user.user_metadata in the application.
SAML attribute mapping configuration fields
SAML attribute mapping fields: name (primary SAML attribute name to look for, matched against both Name and FriendlyName case-insensitively), names (array of fallback attribute names to try in order), default (default value if attribute not present), array (set to true to collect all values for multi-valued attributes like groups).