Rotate the channel assertion key
Channel authentication signs identity assertions with one RSA key. Supabase Auth verifies them through the API's published JWKS. Rotation publishes the new public key before anything signs with it, and keeps the old public key until nothing signed with it is still in flight.
Prerequisites
- OpenSSL 3 and Node.js 22 on the machine where you generate keys.
- Access to the API's secret configuration and a way to deploy the API.
Generate keys on a trusted machine and move them straight into your secret store. Never commit them or paste them into tickets or chat.
Rotate the key
-
Generate a new RSA key:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out assertion-key.pem -
Export the current key's public half as a JWK array. Pass the current private key file:
node -e 'const {createPublicKey}=require("node:crypto");const {kty,n,e}=createPublicKey(require("node:fs").readFileSync(process.argv[1])).export({format:"jwk"});console.log(JSON.stringify([{kty,n,e}]))' current-assertion-key.pem -
Update the API's configuration and deploy it:
Variable New value CHANNEL_AUTH_ASSERTION_PRIVATE_KEYThe contents of assertion-key.pemCHANNEL_AUTH_ASSERTION_RETIRED_PUBLIC_KEYS_JSONThe output of step 2 Expected result:
GET https://<api-origin>/.well-known/jwks.jsonlists two keys. The first is the new key, and new assertions carry itskid. -
Keep the retired key published while Supabase Auth may still verify an assertion against it. Assertions live 60 seconds, but Supabase Auth also caches the JWKS, and this project has not measured for how long. Keep the retired key for at least 24 hours unless you have confirmed a shorter cache time for your Auth version.
-
Clear
CHANNEL_AUTH_ASSERTION_RETIRED_PUBLIC_KEYS_JSONand deploy again. Expected result: the JWKS lists one key.
Rotation does not affect existing users. People are identified by their stable channel identifier, not by key, and Supabase sessions do not depend on the assertion key once issued.
Revoke a compromised key
Whoever holds the key can sign assertions that Supabase Auth turns into a session for any existing channel identity. Remove it at once instead of waiting out an overlap window. Sign-ins fail during the switch, and that is acceptable.
- Generate a new key, set it as
CHANNEL_AUTH_ASSERTION_PRIVATE_KEY, leaveCHANNEL_AUTH_ASSERTION_RETIRED_PUBLIC_KEYS_JSONempty, and deploy. - Confirm that the JWKS no longer lists the compromised
kid. - Treat sessions issued since the leak as untrusted. Revoke them from Supabase Auth with operator credentials, and review the API logs for exchanges you cannot explain.