Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Signing and encryption

All domains, identifiers, keys, and tokens in these examples are fictional, and key values are shortened.

The printer has decided to receive its UserInfo responses signed and encrypted, and to sign and encrypt its request objects. Before the photo service can encrypt anything for the printer, the printer needs a key the photo service can encrypt to, and the two sides need to agree on algorithms.

A key for encryption

The printer already publishes a key set at https://printer.example/oauth/jwks.json, registered with the photo service as its jwks_uri. It holds printer-2026-10, the elliptic curve key that signs the printer's client assertions and request objects. The printer adds a second key, an RSA key pair used only for encryption:

GET /oauth/jwks.json HTTP/1.1
Host: printer.example

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600

{
  "keys": [
    { "kid": "printer-2026-10", "kty": "EC", "crv": "P-256", "use": "sig", "alg": "ES256", "x": "...", "y": "..." },
    { "kid": "printer-enc-2026-10", "kty": "RSA", "use": "enc", "alg": "RSA-OAEP-256", "n": "...", "e": "AQAB" }
  ]
}

The new key has its own identifier, printer-enc-2026-10, and two fields that say what it is for. use is enc, for encryption, where the signing key says sig. Once a key set contains both kinds, every key in it must carry use, so that nobody encrypts to a signing key or verifies with an encryption key. alg ties the key to a single algorithm, RSA-OAEP-256. Some algorithms could use one key for both purposes, but the specifications advise against it, for the reasons Storing and using keys gave for limiting each key to one job.

The private half stays with the printer's sign-in service, ideally in a key service that decrypts on request without ever releasing the key. The Cache-Control header tells the photo service how long it may keep its copy of this set before fetching it again, which will matter when the key is replaced.

Registering the choices

The printer records its choices in its registration with the photo service. Registering a relying party introduced id_token_signed_response_alg. The rest of the fields come in families:

{
  "client_id": "photo-printer",
  "jwks_uri": "https://printer.example/oauth/jwks.json",
  "id_token_signed_response_alg": "RS256",
  "userinfo_signed_response_alg": "RS256",
  "userinfo_encrypted_response_alg": "RSA-OAEP-256",
  "userinfo_encrypted_response_enc": "A256GCM",
  "request_object_signing_alg": "ES256",
  "request_object_encryption_alg": "RSA-OAEP-256",
  "request_object_encryption_enc": "A256GCM"
}

Only the fields this lesson discusses are shown. A field ending in _signed_response_alg names the algorithm the photo service must sign with. For UserInfo, registering one is what turns the response from plain JSON into a signed JWT. The printer leaves out id_token_encrypted_response_alg, so its ID tokens stay signed only.

Encryption needs two algorithms, so each encrypted message has a pair of fields. The _alg field names the key management algorithm, which protects the key that the content is encrypted with. The _enc field names the content encryption algorithm, which encrypts the message itself. An _enc value cannot be registered without its _alg, and when only the _alg is registered, the _enc defaults to A128CBC-HS256. The printer chooses A256GCM explicitly.

The request object fields read slightly differently, because here the printer is the sender. request_object_signing_alg is a rule the photo service enforces: every request object from the printer must be signed with ES256, and any other is rejected. The encryption pair only declares what the printer may use. To encrypt, the printer needs a public key belonging to the photo service, so the photo service's key set now includes one, photos-enc-2026-09, marked use enc, beside its signing keys photos-rs-2026-09 and photos-2026-09.

Every algorithm the printer registers must be one the photo service supports. Its metadata lists them under names such as userinfo_encryption_alg_values_supported, userinfo_encryption_enc_values_supported, and request_object_encryption_alg_values_supported.

Inside an encrypted response

With the registration in place, the printer's next UserInfo request looks the same as ever. The response does not:

HTTP/1.1 200 OK
Content-Type: application/jwt

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoicHJpbnRlci1lbmMtMjAyNi0xMCIsImN0eSI6IkpXVCJ9
.(encrypted key)
.(initialization vector)
.(ciphertext)
.(authentication tag)

The line breaks are for display, and the last four parts are named rather than shown. A UserInfo response that is signed or encrypted is a JWT, with the content type application/jwt. This one is a JWE in its compact form: five Base64url parts separated by dots, where a signed JWT has three. The first part, the protected header, decodes to:

{
  "alg": "RSA-OAEP-256",
  "enc": "A256GCM",
  "kid": "printer-enc-2026-10",
  "cty": "JWT"
}

Encrypting the whole response with the printer's RSA key would be slow, and RSA can only encrypt a small amount of data at a time. As Secrets and key pairs described for TLS, the key pair protects a fresh shared key instead, and the shared key does the bulk of the work. The five parts follow that design:

PartWhat it holds
Protected headerThe algorithms and key identifier. Anyone can read it, and it is covered by the integrity check.
Encrypted keyA random 256-bit content encryption key, generated for this one message and encrypted to printer-enc-2026-10 with RSA-OAEP-256.
Initialization vectorA value the content encryption needs alongside the key, chosen fresh for each message.
CiphertextThe message, encrypted with the content encryption key using AES-GCM.
Authentication tagThe integrity check. If the ciphertext, the initialization vector, or the protected header has changed, decryption fails.

So alg says how the content encryption key reaches the printer, and enc says how the content itself is encrypted. JWE allows only authenticated encryption for enc, which checks integrity as it decrypts. kid tells the printer which of its private keys to use, and cty set to JWT says that the decrypted content is itself a JWT.

Other key management algorithms change the second part. With ECDH-ES, the sender generates a temporary elliptic curve key pair, places its public half in the header as epk, and both sides derive the content encryption key from it and the recipient's key, so the encrypted key part is empty. You may also meet RSA1_5, an older RSA scheme that current guidance says to avoid, for reasons that come up in Key selection and validation failures.

Signed, then encrypted

Decrypting the ciphertext gives the printer an ordinary signed JWT, which begins eyJhbGciOiJSUzI1NiIsImtpZCI6InBob3Rvcy1ycy0yMDI2LTA5IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2F1dGgucGhvdG9zLmV4YW1wbGUi... and decodes to:

Header:
{
  "alg": "RS256",
  "kid": "photos-rs-2026-09",
  "typ": "JWT"
}

Claims:
{
  "iss": "https://auth.photos.example",
  "aud": "photo-printer",
  "sub": "user-2048",
  "name": "Robin Park",
  "given_name": "Robin",
  "family_name": "Park",
  "email": "[email protected]",
  "email_verified": true,
  "picture": "https://photos.example/avatars/user-2048.jpg",
  "locale": "en-US",
  "updated_at": 1788220800
}

This is a nested JWT, the structure Building a request object used for encrypted request objects: a signed JWT carried as the content of an encrypted one. The photo service signed your profile with the RSA key that signs its ID tokens, then encrypted the result to the printer. The signed form contains iss and aud, as every signed UserInfo response must.

OpenID Connect requires this order whenever a message is both signed and encrypted, and the order does real work. The signature covers your profile itself, so it keeps its meaning after decryption. The sign-in service can pass the signed JWT to the order service, and the order service can verify that the photo service made these statements without ever holding the printer's decryption key. A signature over the ciphertext would only show who produced one particular encrypted form, and the specification notes that many jurisdictions do not treat signatures over encrypted text as valid. Encrypting last needs no second signature either, because the authenticated encryption already detects any change to the outer layer.

The order also explains the most common mistake in handling nested JWTs. As Choosing which messages to protect pointed out, encryption to the printer's public key says nothing about the sender. A library that decrypts the outer layer and hands back the inner JWT without verifying its signature has accepted a message that anyone could have written. Both layers must pass, each with keys and algorithms the printer chose itself. Key selection and validation failures works through that order and the ways it can fail.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2An encrypted UserInfo response has "alg": "RSA-OAEP-256" and "enc": "A256GCM" in its header. What does each one do?

QUESTION 2 OF 2When a message is both signed and encrypted, why is it signed first?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity