To rotate the SAML Service Provider (SP) signing and encryption keys without breaking existing sessions, follow a four-step process that utilizes a 'dual-key window'. This window allows Identity Providers (IdPs) to discover and cache the new certificate before the old one is decommissioned.
Prerequisites
- Access to the GoTrue environment variables or secrets store.
- Ability to trigger a rolling restart or redeploy of GoTrue.
openssl installed locally.
Step 1: Generate a new key
Generate a PKCS#1 DER key encoded as standard Base64 (no line breaks):
openssl genrsa 2048 | openssl rsa -outform DER | base64 | tr -d '\n'
Requirement: RSA 2048 or larger, public exponent 65537.
Step 2: Announce the new certificate (Dual-key window)
Configure both the current key and the new key in your environment variables:
GOTRUE_SAML_PRIVATE_KEY=<current key — unchanged>
GOTRUE_SAML_PRIVATE_KEY_NEXT=<new key from Step 1>
Redeploy GoTrue. During this phase, both certificates appear in the SAML metadata. If GOTRUE_SAML_ALLOW_ENCRYPTED_ASSERTIONS is enabled, GoTrue will automatically attempt decryption with the old key if the primary key fails.
Verification:
Check that the metadata contains 2 key descriptors:
curl -s https://<your-domain>/auth/v1/sso/saml/metadata \
| xmllint --xpath 'count(//md:KeyDescriptor[@use="signing"])' \
--noout - 2>/dev/null
# Expected: 2
Step 3: Wait for IdP caches to drain
Wait for the longest cache TTL among your IdPs. The minimum recommended wait is 1 hour (based on the cacheDuration=PT1H advertised in the metadata).
Step 4: Promote the new key
Swap the values so the new key becomes the primary, and clear the _NEXT variable:
GOTRUE_SAML_PRIVATE_KEY=<new key from Step 1>
GOTRUE_SAML_PRIVATE_KEY_NEXT= # remove / clear
Redeploy GoTrue. The metadata will now only show the new certificate.
Verification:
Check that the metadata contains 1 key descriptor:
curl -s https://<your-domain>/auth/v1/sso/saml/metadata \
| xmllint --xpath 'count(//md:KeyDescriptor[@use="signing"])' \
--noout - 2>/dev/null
# Expected: 1
And verify the rotation status via the settings endpoint:
curl -s https://<your-domain>/auth/v1/settings \
| jq '.saml_private_key_next_configured'
# Expected: false