OWASPCheat Sheet

CS-JSON_Web_Token JSON Web Token Cheat Sheet

Official page ↗
Project
OWASP Cheat Sheet Series

Introduction

This cheat sheet provides tips to prevent common security issues when using JSON Web Tokens (JWT). JSON Web Tokens (JWT) are security tokens for carrying information (claims), often about a user, an application, etc. (subject). JWTs can provide authenticity of the claims (signed JWT) and/or confidentiality of the claims (encrypted JWT). In addition, JWT defines standard claims. JWTs are used in a wide range of applications such as: • In OpenID Connect, the ID token is a JWT used to represent the identity and attributes of the connected user. • In OAuth 2, the access token used to obtain access to a protected resource can be a JWT. • A JWT is often suggested for “stateless” user sessions. However, this usage is frowned upon. • A DPoP Proof JWT can be used to prove possession of a private key. • In SPIFFE, a JWT-SVID can be used to authenticate a workload. In its most common form (signed JWT), this information is protected by the generating application (issuer) using a signature to ensure it has not been tampered with. This signature prevents attackers, such as a malicious client or user, from forging a token or modifying the claims in an existing token, for example changing the user role from a simple user to an admin or altering the client's login. The JWT can be seen as a protected identity card or certificate about a user, an application, etc. An application (presenter) presents the token to a consuming application (audience) which can verify the token's authenticity and validity and take decisions or actions based on these claims. JWT can also provide confidentiality of the claims (encrypted JWT). Encryption itself is only introduced briefly in Token Confidentiality and JWE, but many aspects of this cheat sheet also apply to encrypted JWTs.

Token Structure

Signed JWTs have the following structure: ``` {base64url(json(header))}.{base64url(json(claims))}.{base64url(signature)} ``` The following elements are present in signed JWTs: • Protected Header: the JWT header contains some information about the token such as the type of token (IANA media type) and the cryptographic algorithms used to protect the token. • Claims: the content JWT is a list of claims (usually about the subject). See the JWT IANA Registry for a list of standard claims. • Signature: a signature in JWT is either a public-key digital signature (using a public/private key pair) or a MAC (using a shared secret). The signature protects both the protected headers and the claims. Example For example, the following example (taken from JWT.IO): ```text eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30 ``` The first part (protected header) can be decoded into: ```json { "alg": "HS256", "typ": "JWT" } ``` The second part (claims) can be decoded into: ```json { "sub": "1234567890", "name": "John Doe", "admin": true, "iat": 1516239022 } ``` The last part (signature) guarantees the authenticity of both the header and the claims, either using a public/private key pair (digital signature) or a shared secret (MAC), depending on the `alg` header value. For our example, it is computed as: ```javascript base64url( HMACSHA256( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + "." + "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", key ) ) ```

Considerations about using JWTs

Not using JWTs Before using JWTs to solve your problems, you should consider if they are really necessary for your use case. JWTs are often suggested for “stateless” user sessions. However, if you use JWTs for user sessions, you will need a solution for managing session invalidation. This can be achieved using a deny list of revoked sessions/tokens. If your application implements such a deny list, user sessions won't be completely stateless anymore which might defeat the benefits of stateless sessions. You might want to consider using a plain session system and follow the advices from the dedicated session management cheat sheet. Public-key Signatures vs. MAC A signed JWT can be authenticated using either a digital signature or a MAC: • When using a digital signature, the issuer of the token uses its private key to generate a signature. The audience of the token can use the associated public key to verify the authenticity of the token. Whereas the private key must only be known by the issuer, the public key can be public. • When using a MAC, a shared secret is shared between the issuer and the audience. The *same* shared secret is used by the issuer to generate the token and by the audience to verify the authenticity of the token. The two approaches differ on how credentials are managed. When using a digital signature: • The issuer can reuse the same public key for many different audiences. • The audience of the token only need public information to validate the token authenticity which removes the risk of secret leakage by the audience. • Because the public key does not need to be secret, it can easily be distributed (eg. by publishing it at a public HTTPS URI). • This makes key rotation simpler as well. • Traditional digital signature schemes might be broken by post quantum computers in the future. They would need to be replaced with post-quantum digital signature schemes which heavier are traditional signature schemes. When using a MAC: • A different secret must be used for each (issuer, audience) pair. If, for example, the same secret is reused for difference audiences, one audience can forge a token (impersonating the issuer). • Secret keys must obviously not be published at a public HTTPS URI. Some solution for secret distribution and rotation must be found. • MAC are much faster than digital signatures (but this is usually negligible in practice). Using a MAC may be interesting in the following cases: • The issuer of the token is the sole audience of the token. Even in this case, it might be easier to use digital signature for secret rotation/distribution. • The issuer of the token is the audience. In this case, there is no problem of secret rotation/distribution. Public-key Signatures | Signature scheme | Identifier | Type | Status |-----------------------|---------------------------------|--------------|--------- | EdDSA | EdDSA, Ed448, Ed25519 | Traditional | Recommended, limited support | ECDSA | ES256, ES384, ES512 | Traditional | Recommended | RSASSA-PSS | PS256, PS384, PS512 | Traditional | Recommended | RSASSA-PKCS1-v1_5 | RS256, RS384, RS512 | Traditional | Not recommended | Hybrid ML-DSA / EdDSA | ML-DSA-44-Ed25519, etc. | PQ/T hybrid | Draft | Hybrid ML-DSA / ECDSA | ML-DSA-44-ES256, etc. | PQ/T hybrid | Draft | ML-DSA | ML-DSA-44, ML-DSA-65, ML-DSA-87 | Post-quantum | Very limited support at best Explanations: • Support for EdDSA in JWT implementations is currently limited. • Generating ECDSA signatures may be dangerous on embedded systems where the quality of the randomness may be problematic. In this case, you the implementation must use deterministic ECDA as defined in RFC 6979. • Post-quantum signatures (ML-DSA) or hybrid post quantum signatures are designed to be resistant against quantum computers. However, they produce very large signatures, resulting in very large JWTs. Their usage is probably not justified at the moment unless you need signatures with a long validity. Key management: • Do not reuse the key pair for another purpose (eg. for encryption). • Using the same key for authenticating different types of JWTs is fine as long as this does not introduce a risk of token type confusion. • Do not publish your private key! MAC | Signature scheme | Identifier | Status |-----------------------|---------------------------------|--------------- | HMAC with SHA-2 | HS256, HS384, HS512 | Recommended Secret management: • Do not reuse the same secret for another purpose (eg. for encryption). • Using the same key for authenticating different types of JWTs is fine as long as this does not introduce a risk of token type confusion. • Do not reuse the same secret with another audience. • Do not reuse the same secret with another issuer. • Do not use a password as MAC secret. • The secret must be generated using a local, cryptographically secure secret generator. • The secret must have at least the same size as the output (eg. 256, 384 and 512 bits respectively for HS256, HS384 and HS512). • Do not publish your secret key! • The secret must have at least 160 bits of entropy. • For HMAC, the secret should be at least as long as the output size. Valid HMAC secret generation example: ```python import secrets secret_for_hs256 = secrets.token_bytes(256//8) secret_for_hs512 = secrets.token_bytes(512//8) ``` Invalid HMAC secret generation: ```python import random import secrets # Using a password/passphrase is not OK: bad_secret = b"MyProject2026" # Using a hardcoded secret is not OK: bad_secret = urlsafe_b64decode(b'KYkbbclxtjJMiHzoPvuahOfarej0VV-nQZPFxK0hyro=') # Not a secure randomness source: bad_secret = random.randbytes(256//8) # Not enough entropy: bad_secret = secrets.token_bytes(128//8) # Not enough entropy for HS512 meh_secret_for_hs512 = secrets.token_bytes(256//8) ``` Header fields The following table lists some important JWT (JOSE) header parameters for security purpose. | Parameter | Semantic | Security impact |-----------------|----------------------------------------|---------------- | `alg` | Signature algorithm | Signature algorithm used, risk of key type confusion | `typ` | Media type | Protection against token type confusion | `jku` | Verification key (URL to the JWK) | Risk of untrusted key usage, risk or SSRF | `x5u` | Verification key (URL to certificate) | Risk of untrusted key usage, risk or SSRF | `jwk` | Verification key (JWK) | Risk of untrusted key usage | `kid` | Verification key (key ID) | Risk of untrusted key usage | `x5c` | Verification key (certificate chain) | Risk of untrusted key usage | `x5t` | Verification key (certificate hash) | Risk of untrusted key usage | `x5t#S256` | Verification key (certificate hash) | Risk of untrusted key usage See the header parameters subregistry for a list of standard JWT (JOSE) header parameters. Claims The following table lists some important JWT claims for security purpose. | Parameter | Semantic | Security impact |-----------------|----------------------------------------|---------------- | `exp` | Expiration | Token validity | `nbf` | Not valid before | Token validity | `status` | Reference to token status list | Token revocation, risk of SSRF | `iss` | Issuer | Scoping of claims (eg. `iss`), risk of untrusted issuer, risk of SSRF | `aud` | Audience | Protection against audience confusion | `sub`, `sub_id` | Subject identifier | Subject/user identification, risk of cross-issuer user impersonation | `jti` | Token identifier | Audit (logs) | `iat` | Issuance timestamp | Audit (logs) | `azp` | Authorized Party (OIDC) | Audit (logs), authorization | `client_id` | Client (OAuth 2) | Audit (logs), authorization | `auth_time` | Authentication timestamp (OIDC) | Enforcing authentication freshness | `acr` | Authentication class | Enforcing authentication strength (eg. MFA) | `amr` | Authentication method reference | Enforcing authentication strength (eg. MFA) | `cnf` | Token holder (public) key | Sender constrained token | `may_act` | Authorized Actor (impersonation/delegation) | Risk of cross-issuer user impersonation | `act` | Actor (delegation, “on behalf of”) | Audit (logs), risk of invalid actor imputation | `scope` | Token restriction (OAuth 2) | Authorization | `roles` | User roles | Authorization, risk of spoofed cross-issuer authorization | `groups` | User groups | Authorization, risk of spoofed cross-issuer authorization | `entitlements` | User entitlements | Authorization, risk of spoofed cross-issuer authorization | `authorization_details` | Fine grained authorizations | Authorization, risk of spoofed cross-issuer authorization See the JSON Web Token Claims subregistry for a list of standard JWT claims. Many implementation have built-in support for validating core JWT claims such as `nbf`, `exp`, `iss` and `aud`.

Threats on JWTs

See RFC 8725 for a discussion on threats and vulnerabilities related to JWT. Unsecured JWTs Some JWT libraries, used to accept unsecured JWTs by default (`"alg":"none"`). In this case, an attacker would be able to forge their own JWTs: depending on the application, they might be able to impersonate arbitrary users, obtains arbitrary authorizations, etc. This issue should now be fixed in JWT libraries. Mitigation: • Make sure that `"alg":"none"` is not accepted by your JWT parser. It should be disabled by default by recent implementations. Key type confusion Some JWT implementations would accept to use a public key intended for public-key digital signature as if it was a secret key used for MAC verification. In this context, an attacker could forge a MAC-based JWT by using the public key of the real issuer as if it was a secret key. This threat is also called “key confusion” or “algorithm confusion”. Example of legitimate token issuance: ```python token = jwt.encode(claims, private_key_bytes, algorithm="ES256") ``` Example of attacker forging a token based on key type confusion: ```python token = jwt.encode(claims, public_key_bytes, algorithm="HS256") ``` Example of validation potentially vulnerable to key type confusion: ```python # If the token is using a MAC, the library might interpret the public key bytes as a MAC secret: decoded = jwt.decode(token, public_key_bytes, algorithms=jwt.algorithms.get_default_algorithms()) ``` Note: this issue is mitigated in recent versions of the PyJWT library by detecting whether a MAC key appears to be a public key (in PEM of SSH format). Mitigations (at validation): • use a library which is not vulnerable to the issue (eg. strong-typing of the type of key); • chose the key depending on the requested signature algorithm or validate that the key used for validation is consistent with the signature algorithm; • if possible, hardcode the accepted algorithms and do not mix public-key digital signatures algorithms and MAC algorithms. Example of validation not vulnerable because MAC algorithms are not accepted: ```python decoded = jwt.decode(token, public_key_bytes, algorithms=["ES256"]) ``` Illustrative signature verification using joserfc's typed key import and a trusted EC public key. Claim validation is still required before accepting the token. Replace the example key with your issuer's trusted public key; `encoded` is the received token string. ```python from joserfc import jwt, jwk public_jwk = { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", } public_key = jwk.import_key(public_jwk) decoded = jwt.decode(encoded, public_key, algorithms=["ES256"]) ``` References: • Algorithm confusion attacks; • CVE-2022-29217, Key confusion through non-blocklisted public key formats (PyJWT); • CVE-2023-48223, JWT Algorithm Confusion in fast-jwt. Trusting key material named in the token header A JWS header can carry the verification key itself or a pointer to it: `jwk` (an embedded key), `jku` (a URL to a JWK Set), `x5u` (a URL to an X.509 certificate) and `x5c` (an embedded certificate chain), alongside the key selection hints `kid`, `x5t` and `x5t#S256`. An application that resolves or selects its verification key from these header parameters, without tying the result back to something it already trusts, can be steered into trusting a key the attacker controls, because the header is unauthenticated attacker input. An attacker can forge their own token, include their own public key in `jwk`, or point `jku` or `x5u` at a JWK Set or certificate they host, and sign the token with the matching private key. A verifier that trusts the key it has just read from the token accepts the forgery. An attacker can also try to smuggle a symmetric key through the same parameters, in the hope that the implementation will use it for MAC verification. These parameters have legitimate uses, so the distinction is anchoring rather than avoidance. `x5c` and `x5u` are usable where the certificate chain validates up to an anchor already trusted for that issuer, and `kid`, `x5t` and `x5t#S256` are the normal way to choose which key from an already configured JWKS should verify a given token. What must not happen is treating any of them as the source of trust rather than as a pointer within it. Mitigations: • Do not take the verification key from the token unless that key can be tied, through a chain of trust, to a root trust anchor associated with the issuer. • Prefer trust material established out of band, such as a pinned key or the `jwks_uri` published in the issuer's metadata. • Validate or sanitize `kid` before using it in a lookup, since it also reaches databases and directories as an injection vector. • Where keys are fetched by URL, see the Server Side Request Forgery Prevention Cheat Sheet. References: • RFC 8725, Do Not Trust Received Claims; • CVE-2018-0114, a key embedded in the JWS header trusted for verification. Issuer and audience confusion A JWT typically carries the `iss` (issuer) claim to say who created it and the `aud` (audience) claim to say which party is expected to consume and validate it. Neither claim is mandatory, and some designs omit one deliberately: a token an application issues and consumes itself, under a key used for nothing else, or an SD-JWT whose audience is established by the accompanying key binding JWT rather than by an `aud` claim. Where a key does speak for more than one issuer or more than one recipient, however, a verifier that checks only the signature and the expiration accepts any token that key has signed, whatever `iss` and `aud` it carries. An attacker holding such a token, whether legitimately issued to them or obtained from a service they control, may then be able to replay it against a different recipient. RFC 8725 calls this a substitution attack. Two variants are worth separating, because different checks defeat them. Audience confusion. An attacker presents a token issued for one service to a second service that trusts the same issuer. If the second service does not require its own identifier in `aud`, the token is accepted. For example, a token minted for a low-privilege service is replayed against an internal API, and the attacker gains access that was never granted. On a different axis, a third-party application that legitimately receives tokens for its own use can replay one against another application, first-party or third-party, reaching data or operations it was never granted. Issuer confusion. An attacker presents a token from a different issuer that the verifier also trusts, such as a partner tenant or a self-service account at a public identity provider. Comparing the `iss` string alone does not stop this if the verifier resolves its verification key independently of `iss`. A verifier that looks up the key by `kid` across the union of several trusted issuers' JWK Sets will accept a token whose `iss` names one issuer and whose `kid` names a key belonging to another: the signature verifies against the key that `kid` selected, and the `iss` and `aud` comparisons pass because the attacker set both to what the verifier expects. No key needs to be stolen, and the deployment need not be multi-tenant. Mitigations: • Validate `iss` against what the deployment trusts, as a case-sensitive comparison of the whole string including scheme and path. Where a single issuer is expected this is an equality check. Where issuers are provisioned dynamically, through a discovery protocol for example, it becomes membership of an explicit allowlist of issuer identifiers, rather than acceptance of whatever `iss` the token presents. • Select the verification key from the set bound to the validated `iss`, for example that issuer's `jwks_uri`; never verify against a union of keys from several issuers. In multi-tenant deployments this means resolving the key set from an allowlist keyed by issuer rather than searching every tenant's keys. • Validate that the recipient's own identifier is present in `aud`, whether `aud` is a single string or an array of strings. • Where the deployment relies on these claims, reject tokens in which `iss` or `aud` is missing. RFC 7519 makes both optional, so this is a deployment decision rather than a specification requirement: a verifier with no `iss` has nothing to bind the key to, and one with no `aud` cannot tell whether the token was meant for it, but a profile that establishes either by other means does not need the claim itself. The related case, where the key material itself is taken from the token rather than merely selected by it, is covered in Trusting key material named in the token header. Where verification uses a MAC, see also the secret reuse points under MAC. Example of strict validation in Python with PyJWT, resolving the key from the expected issuer's JWK Set rather than accepting one supplied independently: ```python import jwt ISSUER = "https://auth.example.com/" AUDIENCE = "https://api.example.com/v1/payments" # Keys come from this issuer's JWK Set only (its published jwks_uri), # so a `kid` naming a key of some other trusted issuer cannot verify # this token. jwks_client = jwt.PyJWKClient("https://auth.example.com/.well-known/jwks.json") signing_key = jwks_client.get_signing_key_from_jwt(token) decoded_payload = jwt.decode( token, signing_key, algorithms=["ES256"], issuer=ISSUER, audience=AUDIENCE, options={"require": ["exp", "iss", "aud"]}, ) ``` Note: in PyJWT the `issuer` and `audience` arguments perform the validation. The `verify_iss` and `verify_aud` options are enabled by default and gate checks that do nothing on their own, so a token is only checked against an expected issuer and audience when those arguments are passed. References: • RFC 8725, Substitution Attacks; • RFC 8725, Validate Issuer and Subject; • RFC 8725, Use and Validate Audience; • RFC 7519, "iss" (Issuer) Claim; • RFC 7519, "aud" (Audience) Claim. Cross-JWT and token type confusion Cross-JWT (or token type) confusion occurs when validation rules fail to distinguish token kinds, allowing a token issued for one purpose (such as an ID or password-reset token) to be accepted as another (such as an access token). Overlapping claims or shared signing keys are common enabling conditions (see RFC 8725 §2.8). Mitigations: • Use explicit typing (`typ`): Set the `typ` header parameter to a specific media type distinguishing the token's purpose, such as `"at+jwt"` for OAuth 2.0 access tokens (RFC 9068), `"logout+jwt"` for logout tokens (OpenID Connect Back-Channel Logout 1.0 §2.4), or custom types (e.g., `example-reset+jwt`) for internal tokens. • Validate `typ` at the verifier: For token profiles that require or reliably provide explicit typing, reject tokens with missing or unexpected `typ` values at that endpoint. Note that `typ` is case-insensitive and the `application/` prefix may be omitted (RFC 7515 §4.1.9). • Use mutually exclusive validation rules: Where explicit typing cannot be enforced interoperably (e.g., standard OIDC ID tokens omitting `typ`), distinguish token kinds using separate signing keys, required claims, or strict `iss` and `aud` isolation (RFC 8725 §3.12). Example of validation enforcing explicit token type: ```python import jwt # Verify signature and standard claims first decoded = jwt.decode_complete( token, public_key, algorithms=["ES256"], audience="https://api.example.com", issuer="https://auth.example.com", options={"require": ["exp", "iss", "aud"]}, ) # Enforce explicit token type from the verified header typ = str(decoded["header"].get("typ", "")).lower() if typ not in ["at+jwt", "application/at+jwt"]: raise jwt.InvalidTokenError("Invalid token type: expected at+jwt") ``` References: • RFC 8725 §3.11, Use Explicit Typing; • RFC 8725 §3.12, Use Mutually Exclusive Validation Rules for Different Kinds of JWTs; • RFC 9068, JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens.

JWT revocation

Token Status List If revocation of the JWTs by the issuer is needed, the Token Status Lists (TSL) can be used: • the JWT contains the URI of a TSL; • the TSL aggregates the revocation status of several tokens in compressed form; • the consumer of the token can fetch the TSL to obtain the revocation status of the JWT. The issuer includes a `status` claim in the JWT. This claims contains the URI of the associated TSL and the index of the status of the JWT within this list: ```json { "iss": "https://issuer.example/", "sub": "NsxuACbpJ9N7Ix96aWrYxHX-EZ4", "iat": 1783635268, "nbf": 1783635268, "exp": 1783653268, "status": { "status_list": { "idx": 6, "uri": "https://issuer.example/tsl/JAffke55FR5gtJQ_rtktWkSaTlI" } } } ```

Replay protection

JWT denylist In some cases, the consumer of the token might want to maintain a JWT denylist. This might be for example used a simple form of JWT replay protection or as a workaround for the “stateless session” invalidation problem. A JWT deny list can typically be implemented based on the `jti` and `iss` claims: ```python def revoke_token(claims): jti = claims.get("jti") iss = claims.get("iss") exp = claims.get("exp") deny_list.insert((jti, iss), exp) def is_token_revoked(claims) -> bool: jti = claims.get("jti") iss = claims.get("iss") return deny_list.contains((jti, iss)) ``` Depending on the application and the type of JWT, other claims might be more suitable. Warning: Using the raw JWT or a secure hash of the JWT (`SHA-256(token)`) as the denylist key is *not safe* and might expose the application to denylist bypass through JWT malleability. An attacker in possession of a revoked JWT might be able to modify an alternative representation of the JWT that still passes signature verification: • because of non-strict JWT parsing of the JWT implementation; • for ECDSA JWTs, because of the malleability of ECDSA signatures. ```python # Not secure. Might be vulnerable to JWT malleability: def unsafe_revoke_token(claims): exp = claims.get("exp") token_hash = hashlib.sha256(token.encode("utf-8")).digest() deny_list.insert(token_hash, exp) ``` Before implementing such a JWT denylist, you should consider whether there is a better solution for your problem: • Token Status List is a scalable solution for revocation of the JWT by the issuer. • Freshness and replay protection can often by implementing by using a `nonce` bound to the session in the JWT claims. This approach is used in OpenID Connect. • Token reuse can be mitigated by using short expiration time in the JWT. • Sender-constrained access tokens, such as DPoP-bound tokens or mutual-TLS certificate-bound tokens, limit an attacker's ability to use a stolen token without its associated private key. They do not prevent token disclosure. Protect tokens and keys, use HTTPS, and prevent untrusted code from using the client's signing key.

Token Confidentiality and JWE

Signed JWTs are not confidential A signed JWT (JSON Web Signature, JWS) provides integrity and authenticity, but not confidentiality. The payload is only base64url encoded, not encrypted, so anyone who obtains the token can read every claim. With a MAC (`HS*`), a valid signature also only proves that the token was produced by some holder of the shared secret, see Public-key Signatures vs. MAC. TLS prevents the token from being read in transit, but the claims remain exposed elsewhere: in application logs, in browser storage, in referrer headers, and to any intermediary that terminates TLS. Omitting privacy-sensitive information from a JWT is the simplest way of minimizing privacy issues. Prefer keeping sensitive data server-side behind an opaque reference token. Use JWE only when the claims must travel with the token to a party that cannot resolve them with the issuer. Using JWE When claims must be kept confidential, use JSON Web Encryption (JWE). JWE uses two algorithms: • `alg`: the key management algorithm, which encrypts or agrees upon the Content Encryption Key (CEK) for the intended recipient (for example `RSA-OAEP-256` or `ECDH-ES+A256KW`). • `enc`: the content encryption algorithm, which encrypts the payload using authenticated encryption (for example `A256GCM`). JWE provides confidentiality and ciphertext integrity, not issuer authentication. With a public-key `alg`, anyone holding the recipient's public key can produce a token that decrypts successfully, so never make authorization decisions on claims from an unsigned JWE. When both authenticity and confidentiality are needed, use a nested JWT: sign the claims first (JWS), then encrypt the result (JWE). This prevents attacks in which the signature is stripped, leaving just an encrypted message, as well as providing privacy for the signer. In the outer JWE, the `cty` header MUST be set to `JWT` to signal the nesting. When consuming a nested JWT, decrypt the outer JWE and verify the inner JWS signature, rejecting the token if either step fails. Both the outer and the inner operations MUST be validated: successful decryption on its own proves nothing about who issued the claims. Two further requirements apply to JWE: • Accept only an allowlisted `alg`/`enc` pair and bind each key to a single algorithm. Never let the token header select the algorithm, because this enables a downgrade attack that can recover the CEK. • Do not compress the claims before encryption (the `zip` header), because compressed data often reveals information about the plaintext. Note: Full JWE implementation guidance is out of scope for this cheat sheet and will be addressed in a dedicated JWE cheat sheet.

References

Related

Referenced by OWASP · cheat sheet (1)