This page is a focused account of JSON Object Signing and Encryption (JOSE): what each specification covers, how a compact signed token and a compact encrypted token are built, and which checks a recipient applies before trusting the claims. Login grants and consent screens live on the OAuth 2.0 page linked below.
What the JOSE specifications are
JOSE is JSON Object Signing and Encryption. RFC 7165 (April 2014) uses that name for the use cases. RFC 7520 (May 2015) uses it for the worked examples. The documents that define the formats were published together in May 2015:
| Document | What it defines |
|---|---|
| RFC 7515 | JSON Web Signature (JWS): content secured with a digital signature or a message authentication code (MAC) |
| RFC 7516 | JSON Web Encryption (JWE): content encrypted with authenticated encryption |
| RFC 7517 | JSON Web Key (JWK): a cryptographic key as a JSON object, and a JWK Set as a collection of those keys |
| RFC 7518 | JSON Web Algorithms (JWA): the algorithm identifiers and key parameters the other documents share |
| RFC 7519 | JSON Web Token (JWT): a claims set carried in a JWS or a JWE |
JWA is a registry of names such as HS256, RS256, and A256GCM. It is the vocabulary inside a header. RFC 8725 (February 2020), also BCP 225, updates RFC 7519 with deployment guidance after several widely published implementation attacks. The checks at the end of this page follow that BCP.
flowchart TD jose["JOSE"] jose --> jws["JWS: signature or MAC"] jose --> jwe["JWE: authenticated encryption"] jose --> jwk["JWK: a key as JSON"] jose --> jwa["JWA: algorithm names"] jose --> jwt["JWT: a claims set"] jwt --> signed["Compact JWS, three segments"] jwt --> encrypted["Compact JWE, five segments"]
A JWT is a claims set
RFC 7519 defines a JWT as a string that represents a set of claims in a JSON object, encoded in a JWS or a JWE, so the claims can be signed or MACed, encrypted, or both. The suggested environments are space-constrained ones such as an HTTP Authorization header and a URI query parameter, which is why the encoding is URL-safe. The application picks the transport: a header, a query parameter, or a cookie.
A claim is one piece of information asserted about a subject, written as a name and a value. The JWT Claims Set is the JSON object that holds those pairs. The names are strings. The values are any JSON value. When the JWT is a JWS, that object is the JWS payload. When the JWT is a JWE, that object is the plaintext that gets encrypted. JWT is the claims-set specification. The cryptographic work remains JWS or JWE.
A JWT always uses compact serialization. JWS and JWE also define a JSON serialization: JWS JSON serialization can carry more than one signature on the same payload, and JWE JSON serialization can encrypt the same plaintext to more than one recipient. Those JSON objects are valid JWS or JWE values. They are a different representation from a JWT, which RFC 7519 limits to the compact form.
Count the period characters. A compact JWS has three base64url segments and two periods. A compact JWE has five segments and four periods. An empty segment still keeps its period.
Each segment is base64url, the URL- and filename-safe alphabet in section 5 of RFC 4648, with every trailing = omitted and with no line breaks. The characters + and / from ordinary Base64 are - and _ here. The base64url encoding of an empty octet sequence is the empty string.
An unsecured JWT is the case where the claims are neither integrity-protected nor encrypted. RFC 7519 section 6 defines it as a JWS whose alg header is none and whose signature segment is empty, so the compact string ends in a period. That form exists for a JWT that is already protected by something outside the token, such as a signature over a larger structure that contains it.
Registered, public, and private claims
RFC 7519 section 4 divides claim names into three classes.
Registered claim names are the seven names in section 4.1, recorded in the IANA JSON Web Token Claims registry. The RFC says none of them is mandatory for every JWT. An application, or a profile such as an OpenID Connect ID Token, decides which of them it requires. A claim the recipient does not understand is ignored, unless that profile says otherwise. Claim names inside one set must be unique. A parser either rejects a duplicate or, following the ECMAScript rule the RFC cites, keeps the lexically last member.
| Claim | Role | Value |
|---|---|---|
iss |
Issuer, the principal that issued the JWT | Case-sensitive string (StringOrURI) |
sub |
Subject the claims are about | Case-sensitive string (StringOrURI), unique within the issuer or globally |
aud |
Recipients the JWT is intended for | One case-sensitive string, or an array of them |
exp |
Expiration time; reject the JWT at that time and after | NumericDate |
nbf |
Not-before time; reject the JWT before that time | NumericDate |
iat |
Time the JWT was issued, so the recipient can judge its age | NumericDate |
jti |
Unique JWT identifier; an application can store it and reject a repeat | Case-sensitive string |
A StringOrURI is a JSON string. If the string contains a colon, it must be a URI. Comparison is case-sensitive, with no rewriting.
A NumericDate is a JSON number: seconds from 1970-01-01T00:00:00Z UTC, ignoring leap seconds. It is seconds, including a possible fraction. The value 1300819380 is 2011-03-22T18:43:00Z. For exp, the current time must be before that instant. For nbf, the current time must be at or after that instant. Implementers may allow a small leeway for clock skew, usually no more than a few minutes.
When aud is present, each principal that is supposed to process the JWT must find itself in that claim. A processor that does not identify with a listed audience rejects the JWT. One audience may be a single string. Several audiences are an array.
Public claim names are names a producer defines for wider use. To avoid collisions, RFC 7519 says a new name should be registered in the IANA claims registry or should be a collision-resistant name, such as a URI the definer controls. The sample claims set in RFC 7519 uses http://example.com/is_root for that reason.
Private claim names are names a producer and a consumer agree on that are neither registered nor public. They can collide with another application’s private name. An application field such as a role is a private claim until it is registered or given a collision-resistant name. Authentication - Terms and concepts separates verifying who the subject is from deciding what that subject may do. A role claim is input to that second decision. The application is what grants the permission.
RFC 8725 names OpenID Connect ID Tokens, and OAuth 2.0 access tokens and refresh tokens, as common places JWTs appear, and says those details are deployment-specific. RFC 6749 (October 2012) describes an access token as a string that is usually opaque to the client. RFC 9068 (October 2021) is a profile for issuing that access token as a JWT. The OAuth 2.0 page is the overview of the authorization framework around that token.
The compact signed form
A compact JWS is three segments:
BASE64URL(UTF8(protected header)) || '.' || BASE64URL(payload) || '.' || BASE64URL(signature)
The header below is the exact UTF-8 that the example signs. alg is required on a JWS and names the signature or MAC algorithm. typ is optional. When a JWT uses it, RFC 7519 recommends the uppercase spelling JWT, so a structure that might hold something other than a JWT can tell them apart. JWT implementations ignore typ; the application reads it.
{"alg":"HS256","typ":"JWT"}The payload is the exact UTF-8 of this claims set. Whitespace is allowed, and RFC 7519 does not require canonical JSON before encoding. Different whitespace is a different signature. Appendix A.1 of RFC 7515 encodes a two-line header with a CRLF and one leading space, and that header’s base64url value is eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9. The one-line header here encodes to a different string.
{"iss":"https://issuer.example","sub":"user-42","aud":"https://api.example","iat":1300815780,"nbf":1300815780,"exp":1300819380}iat and nbf are 2011-03-22T17:43:00Z. exp is one hour later. The audience is a single string, which RFC 7519 allows when there is one recipient.
The JWS Signing Input is the ASCII of the first two segments joined by a period: base64url of the header, a period, base64url of the payload. The signature is computed over that string. For HS256 the MAC is HMAC-SHA-256 (RFC 2104) of the signing input, and the third segment is the base64url of those raw HMAC octets.
The example key is the 32 octets 0x00 through 0x1f. RFC 7518 requires an HMAC key at least as long as the hash output, 256 bits for HS256, so this key meets the length rule. It is a published sequence so the MAC can be reproduced. RFC 7515 section 10.1 says a key should be generated with at least 128 bits of entropy, and more when the application needs it. RFC 8725 requires that advice to be followed, and it says a human-memorable password must not be used directly as an HMAC key. This example has no entropy. As a JWK the same octets are the k member:
{ "kty": "oct", "k": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8", "alg": "HS256"}The compact JWT, with the third segment beginning with - because that character is part of the base64url alphabet, is:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwic3ViIjoidXNlci00MiIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUiLCJpYXQiOjEzMDA4MTU3ODAsIm5iZiI6MTMwMDgxNTc4MCwiZXhwIjoxMzAwODE5MzgwfQ.-vVFbe6GVeHdOCYt_qi32ivtZ3uqG4ZFy2Y05fYmrXcAnyone who has the token can decode the middle segment and read the claims. A signature detects a change. The payload stays readable.
What signing establishes
RFC 7515 defines a JWS as content secured with a digital signature or a MAC. The alg values below are the ones RFC 7518 section 3.1 registers for that job. The requirement column is the JWS implementation requirement in that RFC. A plus means a later revision is likely to strengthen the requirement.
alg |
Operation | JWS requirement |
|---|---|---|
HS256 |
HMAC using SHA-256 | Required |
RS256 |
RSASSA-PKCS1-v1_5 using SHA-256 | Recommended |
ES256 |
ECDSA using P-256 and SHA-256 | Recommended+ |
PS256 |
RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | Optional |
none |
No digital signature or MAC | Optional |
RFC 7519 section 8 is a separate list for JWT implementations: HS256 and none must be implemented, and RS256 and ES256 are recommended. Support for other algorithms is optional. RFC 8725 then tells applications to allow only algorithms they have chosen, and tells libraries to produce or accept none only when the caller asks.
An HMAC shows that the party who produced the tag held the shared MAC key, and that the signing input matches what was signed. Every party who knows that key can produce a matching tag, so any of them could have created it. A verifier of RS256, ES256, or PS256 uses the public key. The private key stays with the signer. That is the check a set of verifiers can make without holding a secret that would also let them mint tokens. RFC 8725 says JWT libraries should produce ECDSA signatures with the deterministic procedure in RFC 6979, because a repeated or predictable per-message random value can expose an ECDSA private key. Verifiers of ordinary ECDSA accept those deterministic signatures with the same ES256 identifier.
Changing either the header or the payload changes the signing input, so a valid MAC or signature fails. The payload of a JWS stays readable. Confidentiality is the job of JWE.
The compact encrypted form
RFC 7516 defines a JWE as an encrypted and integrity-protected message. Compact serialization concatenates five base64url segments:
BASE64URL(UTF8(protected header)) || '.' || BASE64URL(encrypted key) || '.' || BASE64URL(initialization vector) || '.' || BASE64URL(ciphertext) || '.' || BASE64URL(authentication tag)
Two header parameters divide the work. Both are required.
algnames how the content encryption key (CEK) is determined: key encryption, key wrapping, direct key agreement, key agreement with wrapping, or direct use of a shared key. The encrypted content is unusable whenalgis unsupported or the recipient has no key for it.encnames the authenticated encryption with associated data (AEAD) algorithm that turns the plaintext into ciphertext and an authentication tag. It must be an AEAD algorithm with a specified key length.
RFC 7518 registers many values. A general JWE picks from that registry. These are the ones this page uses as examples:
| Parameter | Example | Role |
|---|---|---|
alg |
RSA-OAEP |
Encrypt the CEK with RSAES-OAEP using the default parameters |
alg |
RSA-OAEP-256 |
Encrypt the CEK with RSAES-OAEP using SHA-256 and MGF1 with SHA-256 |
alg |
A256KW |
Wrap the CEK with AES Key Wrap and a 256-bit key |
alg |
dir |
Use the shared symmetric key itself as the CEK |
alg |
ECDH-ES |
Agree on the CEK with Elliptic Curve Diffie-Hellman Ephemeral Static |
enc |
A128CBC-HS256 |
AES-128-CBC plus HMAC-SHA-256, a required enc value |
enc |
A256GCM |
AES-GCM with a 256-bit key, a recommended enc value |
A128CBC-HS256 takes a CEK of 32 octets: the first 16 are the HMAC-SHA-256 key, the last 16 are the AES-128 key. The initialization vector is 128 bits, and the authentication tag is the HMAC truncated to 128 bits. A256GCM takes a 256-bit CEK, requires a 96-bit initialization vector, and uses a 128-bit authentication tag. The CEK length follows enc. It is a property of the chosen algorithm.
dir and ECDH-ES produce an empty encrypted-key segment. The compact string still contains the period, so those two segments appear as ... Every other alg in the RFC 7518 table produces an encrypted CEK. The usual hybrid pattern generates a fresh CEK for the plaintext, which can be any length the application needs, and uses alg to protect that short key for the recipient. Direct encryption reuses the long-term shared key as the CEK.
In compact serialization the additional authenticated data is the ASCII of the encoded protected header. The header is integrity-protected and stays unencrypted, so alg, enc, and kid are visible. The authentication tag covers the ciphertext and that header. A tag check that fails means the decryption fails.
The plaintext of an encrypted JWT is the UTF-8 JSON of the claims set, unless the JWT is nested and the plaintext is an inner compact JWT. The header parameter cty stays absent on an ordinary encrypted JWT. The next section is the case where it is required.
Public-key alg values such as RSA-OAEP and RSA-OAEP-256 let anyone who has the recipient’s public key create a JWE for that recipient. Successful decryption shows that the CEK was protected for that key. A MAC or a signature inside the plaintext is what identifies the issuer. RFC 8725 tells applications to avoid RSA1_5 (RSAES-PKCS1 v1.5 key encryption) and to prefer RSAES-OAEP.
The optional zip header names compression applied to the plaintext before encryption. The value RFC 7516 defines is DEF, DEFLATE. Absent zip means no compression. RFC 8725 says compression before encryption should be avoided: ciphertext length can reveal information about the plaintext, and the leak is worse when attacker-controlled data shares the compression with a secret.
Nested JWTs
A nested JWT uses one JWT as the payload of an enclosing JWS, or as the plaintext of an enclosing JWE. RFC 7519 section 5.2 requires the cty (content type) header on that enclosing object, and requires the value JWT, spelled in uppercase for compatibility with older implementations. When there is no nesting, the same section says cty should be left out.
A common arrangement signs the claims and then encrypts that compact JWS. The outer header carries cty of JWT plus the JWE algorithms, for example:
{"alg":"RSA-OAEP-256","enc":"A256GCM","cty":"JWT"}RSA-OAEP-256 and A256GCM are one allowed pair. The requirement for nesting is cty, not those two names. The recipient decrypts the outer JWE, reads a compact JWS, and verifies that inner signature with the issuer’s key. RFC 8725 section 2.3 records libraries that decrypt and then skip the inner signature. Section 3.3 requires every cryptographic operation to be checked, outer and inner, and requires the whole JWT to be rejected when any check fails. An application that expects a signed inner JWT rejects an inner alg of none.
RFC 8725 section 3.11 recommends an explicit type for a new kind of JWT that could be confused with another kind. That typ value goes on the inner JWT, the one whose payload is the claims set. The same section recommends a media type of the form application/example+jwt, with the application/ prefix omitted in the header, so the value looks like example+jwt. Explicit typing is for new uses. Older profiles often ignore typ, so a distinct type does not by itself separate those older tokens.
JSON Web Keys
RFC 7517 defines a JWK as a JSON object that represents a cryptographic key, and a JWK Set as a JSON object whose keys member is an array of JWKs. The kty member is required. It names the key family. RFC 7518 registers EC (elliptic curve), RSA, and oct (an octet sequence, used for symmetric keys). RFC 8037 (January 2017) adds the OKP family for Ed25519, Ed448, X25519, and X448.
An oct key stores the secret in k, base64url of the octets, as in the HMAC example above. RFC 7518 says an alg member should also be present on a symmetric key unless the application has another way to know the algorithm. A symmetric k is a secret. A JWK Set published for verifiers contains public keys.
An RSA or elliptic-curve verifier uses the public members: for RSA, the modulus and exponent; for an EC key, the curve and the public point. The private members stay with the signer or with the party that decrypts. Verification of RS256 or ES256 uses the public key that matches the private key which produced the signature. Possession of the private key is what creates the signature, and what decrypts a JWE encrypted to that key.
kid is an optional case-sensitive hint that says which key was used. Its syntax is not specified. When the key is a JWK, kid is matched to the JWK’s own kid. The hint is a lookup aid. Trust comes from the key the application has already bound to the issuer, and from the signature or decryption check that follows.
jku is an optional URL of a JWK Set. RFC 7515 requires the protocol that fetches it to provide integrity protection: an HTTP GET uses TLS, and the client checks the server identity. RFC 8725 says an application should allow-list those URLs. Fetching whatever URL arrives in the header is a server-side request forgery. The same caution applies to x5u, a URL for an X.509 certificate. kid is also untrusted input: RFC 8725 says to validate or sanitize it so a lookup cannot become SQL or LDAP injection.
Checks before you accept a token
RFC 8725 is the minimum bar for most deployments. A profile can be stricter. The recipient’s application, or the library it configures, does the following.
- Offer an allow-list of algorithms and refuse every other
algorenc. Use each key with exactly one algorithm, and check that pairing when the operation runs. This closes the two attacks in RFC 8725 section 2.1: an attacker rewritesalgtonone, or rewritesRS256toHS256so a library treats the RSA public key as the HMAC secret. - Allow only algorithms that still meet the application’s security bar, and keep the set replaceable. Avoid
RSA1_5. Prefer RSAES-OAEP for RSA key encryption. - Give an HMAC key at least as many bits as the hash output, and draw that key from a generator with enough entropy. A password is not an
HS256key. Passwords, if used at all, belong in the password-based key encryption algorithms, and even those remain open to offline guessing. - Validate every layer. A nested token whose inner signature is skipped, missing, or
nonewhen a signature is required is rejected. - When
issis present, confirm that the keys used on the token belong to that issuer. Whensubis present, confirm that the subject is one the application accepts for that issuer. When one issuer can mint tokens for more than one recipient, requireaudand reject a token whose audience is missing or is some other party. Applyexpandnbffrom RFC 7519, with at most a few minutes of clock skew. - Treat
kid,jku, andx5uas data the sender controls. Allow-list key-set URLs. Validate or sanitizekidso a lookup cannot become SQL or LDAP injection. - Leave out
zip. RFC 8725 says compression before encryption should be avoided. Encode the header and the claims set as UTF-8. - For a new kind of JWT, put an explicit type on the inner token and write validation rules that reject a token of a different kind. Distinct keys, distinct
audvalues, or distinct issuers do the same separation when several kinds share an issuer.
If crit is present, the recipient must understand every header parameter it lists. Otherwise the token is invalid. Producers use crit for extensions, not for the parameters RFC 7515 and RFC 7518 already define.
flowchart TD recv["Receive the compact token"] alg["alg is allowed for this key"] crypto["Every signature and decryption check succeeds"] claims["iss, aud, exp, and nbf pass"] accept["Accept the claims"] reject["Reject the token"] recv --> alg alg -->|no| reject alg -->|yes| crypto crypto -->|no| reject crypto -->|yes| claims claims -->|no| reject claims -->|yes| accept
Authentication Mechanisms in Web Technology: An Overview places this format among the other ways a web application recognizes a caller. The token is the signed or encrypted claims set. The decision to accept it is the list above.
Check your understanding
Question 1 of 9
0 of 9 checked