Encrypted tokens
Everything a client has to do to receive encrypted tokens: choose the key, generate the pair, configure Authio and the app, test, and replace the key.
By default Authio tokens are signed JWTs: anyone holding a token can read its content. With encryption on, they become JWEs that only the key holder can open. This guide covers the whole path, from the console to the app in production. For UserInfo as a JWT, see Signed and encrypted UserInfo.
Which key to use
| Platform's key | Client's own key | |
|---|---|---|
| Access and refresh tokens | Encrypted; only Authio reads them | Encrypted to the client's key |
| ID token | Signed only, for the app to read | Encrypted to the client's key and signed inside |
| What the app configures | Nothing | The private key, in an environment variable |
| Access token claims in the app | Unreadable | Readable once decrypted with the same key |
| Resource servers | Validate by introspection | Validate by introspection |
Use the platform's key to hide the access and refresh tokens from everything outside Authio (browser, logs, proxies) without touching the app. Use the client's own key when the ID token must not travel readable either, not even inside your infrastructure. Either way, encryption hides the content; it does not prevent use: an access token is still valid for whoever holds it.
Only for clients with a back end
Checklist
- Generate (or import) the client's key pair and download the private key.
- Store the private key in the app's secrets, never in the repository.
- Configure the app with the key and deploy it.
- Only then turn encryption on for the client, with the client's own key,
RSA-OAEPand a CBC-HMAC algorithm. - Configure the APIs that receive the access token to validate it by introspection.
- Test a sign-in and a session renewal.
Order matters
Step by step in Authio
Generate the key pair and download the private key
In Clients, open the client and go to Edit → Security → Token Encryption → Generate key pair. The private key is shown once, as a JWK and as a PEM: use Download JWK (or copy it) before closing the dialog. Authio never shows the private key again; losing it means generating another pair.
If you already have an RSA key of 2048 to 4096 bits, use Import private key (JWK or PEM); the
kidof an imported JWK is kept.Store the key in the app
Put the JWK (the downloaded file's content) in the app's secrets: an environment variable, a key vault. See below where each library looks for it, and deploy the app with it.
Turn encryption on for the client
Still in Security → Token Encryption, fill in and save:
Field Value Token Encryption On Encryption key Client's own key JWE Algorithm RSA Key Encryption Algorithm RSA-OAEPContent Encryption Algorithm A256CBC-HS512(or another CBC-HMAC; Authio does not encrypt with AES-GCM)Tokens encrypted to the client's key are always signed inside, so Sign Encrypted Tokens turns on by itself. Prefer
RSA-OAEPtoRSA1_5: some libraries, such as Auth.js v5, cannot open RSA1_5.Test
Sign in: the
id_tokenin the token endpoint's response has five dot-separated parts (a JWE) and the app opens the session as usual. Then let the access token expire (or force a renewal) and check the session carries on.
Configure your app
Once decrypted, the ID token is an ordinary signed JWT, validated against the realm's JWKS as always. The only new thing is the private key.
Next.js (Auth.js v5 and NextAuth v4)
Since version 2.2.0, authio-provider-nextauth decrypts the ID token on its own. Put the private key in AUTH_AUTHIO_ENCRYPTION_KEY: the JWK Authio showed when it generated the pair, or the PEM (PKCS#8 or PKCS#1) when you imported the key. The code does not change.
AUTH_AUTHIO_ENCRYPTION_KEY='{"kty":"RSA","n":"...","e":"AQAB","d":"...","p":"...","q":"...","dp":"...","dq":"...","qi":"..."}'On Auth.js v5 the algorithms are read from the token itself. On NextAuth v4 openid-client only accepts the algorithms it knows: the provider assumes Authio's defaults (RSA-OAEP and A256CBC-HS512); if the client uses others, pass encryption: { alg, enc } to Authio(). On v4, with the key set, the ID token must arrive encrypted: remove the variable if you turn encryption off.
The access token arrives as Authio issued it, since it is what your API receives. To read its claims on the server, or to open the tokens of a renewal your app performs itself, use decryptAuthioToken:
import { decryptAuthioToken } from 'authio-provider-nextauth/v5'
import { decodeJwt } from 'jose'
// Uses AUTH_AUTHIO_ENCRYPTION_KEY; a token that was not encrypted comes back as it is
const claims = decodeJwt(await decryptAuthioToken(token.accessToken)).NET (BFF and OpenID Connect)
In Authio's BFF (Authio.AspNetCore.Bff 0.6.0 or later), set the key in Authio:Bff:EncryptionKey (the JWK or the PEM). It decrypts the ID token and the access token the session's roles and organization are read from, at sign-in and on every renewal, and checks the key at startup.
{
"Authio": {
"Bff": {
"EncryptionKey": "<the private JWK or the PEM, from a secret>"
}
}
}With ASP.NET Core's OpenID Connect handler directly, pass the key in TokenValidationParameters.TokenDecryptionKey.
Any OIDC client
- Receive the
id_tokenfrom the token endpoint: with five parts, it is a JWE. - Decrypt it with the client's private key, accepting the
algandencconfigured on the client (see the JWE header). - The content is a signed JWT: validate signature,
iss,aud,expandnonceagainst the realm's JWKS. - Send the access token to your API as it came. If the app needs its claims, decrypt a copy with the same key.
APIs and resource servers
An API does not hold the key of the client that requested the token, so it cannot open the access token: it validates it by introspection. With Authio.AspNetCore, set Authio:Introspection to IntrospectionOnly; otherwise the API tries to read the token locally and refuses the JWE.
{
"Authio": {
"Introspection": "IntrospectionOnly"
}
}Replacing or removing the key
Generating another pair, importing another key or removing the key makes every token encrypted to the previous one unreadable at once: open sessions need to sign in again. To replace it with the least impact:
- Generate the new pair and download the private key.
- Update the app's secret and deploy right away.
- Let people who were signed in know they will sign in again.
Removing the key moves the client back to the platform's key and turns encrypted UserInfo off.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
JWE decryption is not configured (Auth.js v5) | authio-provider-nextauth is older than 2.2.0, or AUTH_AUTHIO_ENCRYPTION_KEY is not set. Update the package and set the variable. |
unexpected JWE alg received (NextAuth v4) | The client's algorithms in Authio differ from the provider's. Pass encryption: { alg, enc } with the client's values. |
| Sign-in fails right after generating a new key | The app still holds the old key. Update the secret with the new one. |
| I lost the private key | Authio does not show the key again. Generate another pair and update the app; if the affected app is your way into the console, turn the client's encryption off by other means first, and back on afterwards. |
| The app cannot read the access token's claims | With the platform's key it is unreadable to the app by design. With the client's key, decrypt it before reading. |