Skip to content

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 keyClient's own key
Access and refresh tokensEncrypted; only Authio reads themEncrypted to the client's key
ID tokenSigned only, for the app to readEncrypted to the client's key and signed inside
What the app configuresNothingThe private key, in an environment variable
Access token claims in the appUnreadableReadable once decrypted with the same key
Resource serversValidate by introspectionValidate 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

The private key has to live on a server. A SPA or a mobile app has nowhere to keep it, so it cannot open the ID token: use the platform's key (or none) for those clients.

Checklist

  1. Generate (or import) the client's key pair and download the private key.
  2. Store the private key in the app's secrets, never in the repository.
  3. Configure the app with the key and deploy it.
  4. Only then turn encryption on for the client, with the client's own key, RSA-OAEP and a CBC-HMAC algorithm.
  5. Configure the APIs that receive the access token to validate it by introspection.
  6. Test a sign-in and a session renewal.

Order matters

An app without the key cannot open the ID token, and sign-in fails at once. Deploy the app with the key before turning encryption on, and turn encryption off before removing the key from the app.

Step by step in Authio

  1. 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 kid of an imported JWK is kept.

  2. 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.

  3. Turn encryption on for the client

    Still in Security → Token Encryption, fill in and save:

    FieldValue
    Token EncryptionOn
    Encryption keyClient's own key
    JWE AlgorithmRSA
    Key Encryption AlgorithmRSA-OAEP
    Content Encryption AlgorithmA256CBC-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-OAEP to RSA1_5: some libraries, such as Auth.js v5, cannot open RSA1_5.

  4. Test

    Sign in: the id_token in 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.

.env.env
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:

auth.tsTypeScript
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.

appsettings.jsonJSON
{
  "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

  1. Receive the id_token from the token endpoint: with five parts, it is a JWE.
  2. Decrypt it with the client's private key, accepting the alg and enc configured on the client (see the JWE header).
  3. The content is a signed JWT: validate signature, iss, aud, exp and nonce against the realm's JWKS.
  4. 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.

appsettings.json (API)JSON
{
  "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:

  1. Generate the new pair and download the private key.
  2. Update the app's secret and deploy right away.
  3. 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

SymptomCause 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 keyThe app still holds the old key. Update the secret with the new one.
I lost the private keyAuthio 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 claimsWith the platform's key it is unreadable to the app by design. With the client's key, decrypt it before reading.