JSON Web Tokens is a structure that is used to create access_tokens
and id_tokens
.
JWTs are defined by the rfc7519 standard.
The JWT specification defines it as follows:
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
JSON Web Tokens have three segments:
Each segment is a base64 URL-encoded JSON object, and the segments are separated by dots. An example of a JWT:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDEiLCJpc3MiOiJodHRwczovL3Rlc3QuY29tIiwiYXVkIjoidGVzdCIsImV4cCI6MTc0NTUxNjUzMX0.clN2sG9gPVlLnrb4qLpQ9tehgElkIjbRSTGBt4lTRrH7dPRJosSo1erFC_LhmyprhK66n2bhYEPxR0OHdvTxJPCBLky1FX4CYAVwiQtb1ttTH85ibr0b9dkcWFqDxzpLjc98U7zLWkCqOPfG-XIJzmuSzqPxSZlSiDyT7915LlbCDteXaKJHYxNFgWcc2YeJqEW_2qGJl5pnAkkvtl5eVbURkNR-fBUqcE24QKSY-nvOFRp8ebdzs9AMmNVa3GyIx2eRkqJosGilfXwM9rpgQjO1bKVA5PROO7TIAysYHDddV05lVvBRgWwIvXBisM9ej8lDjU0-r9vveOv5gY5GbQ
The header of a JWT contains metadata about the token that is needed to validate its authenticity and to read its contents.
JWTs are split into three parts, separated by dots. The first part represents the header. In the example, the header is: `eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0`. The contents of a segment can easily be read using the following command:
echo "eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsInR5cCI6IkpXVCJ9" | base64 -d
This will yield the following output:
{ "alg": "RS256", "kid": "00000000-0000-0000-0000-000000000000", "typ": "JWT" }
The payload of a token contains the user claims
.
Claims are bits of information about the user and the token.
These are the standard claims defined in by the
OpenId Connect specification:
Claim Type | Description |
---|---|
iss: | Issuer Identifier for the response's issuer. This is a case-sensitive URL using the HTTPS scheme that includes the scheme, host, and optionally, port number and path components, but no query or fragment components. |
sub: | Subject Identifier, a unique and non-reassigned identifier within the issuer for the End-User, intended for use by the client. It should not exceed 255 ASCII characters in length. |
aud: | Audience(s) the ID Token is intended for. It must include the OAuth 2.0 client_id of the Relying Party as an audience value and may include other audience identifiers. In the case of one audience, it may be a single string. |
exp: | Expiration time after which the ID Token should not be accepted. The value represents the number of seconds since 1970-01-01T00:00:00Z (UTC). A small leeway is often allowed to account for clock skew. |
iat: | The time when the JWT was issued, represented as the number of seconds since 1970-01-01T00:00:00Z (UTC). |
auth_time: | The time when the End-User was authenticated, expressed as seconds since 1970-01-01T00:00:00Z (UTC). It is required when a max_age request is made or when the auth_time claim is requested as an Essential Claim. |
nonce: | A string used to associate a client session with an ID Token to prevent replay attacks. It is passed unchanged from the Authentication Request to the ID Token. Clients must verify that this matches the nonce in the request. |
acr: | Authentication Context Class Reference, a string that specifies the authentication context satisfied during authentication. A value of "0" indicates no confidence that the same person is authenticated. |
amr: | Authentication Methods References, an array of strings identifying the authentication methods used. For example, it might include both password and OTP methods. Values should be from the IANA Authentication Method Reference Values registry. |
azp: | Authorized party to which the ID Token was issued. This claim is included when the ID Token is issued for a specific client. |
name | The End-User's full name in displayable form, including all name parts, titles, and suffixes, ordered according to the End-User's locale and preferences. |
given_name | The End-User's given name(s) or first name(s). Some cultures may have multiple given names, all of which can be present and separated by spaces. |
family_name | The End-User's surname(s) or last name(s). Some cultures may have multiple family names or no family name at all; all can be present, separated by spaces. |
middle_name | The End-User's middle name(s). Some cultures may use multiple middle names, or none at all. |
nickname | The End-User's casual name, which may or may not be the same as the given_name. |
preferred_username | The End-User's preferred shorthand name, such as janedoe or j.doe. This value may include special characters, but should not be assumed to be unique. |
profile | A URL to the End-User's profile page. The contents of this page should provide information about the End-User. |
picture | A URL to the End-User's profile picture, which must refer to an image file (e.g., PNG, JPEG, GIF). |
website | A URL to the End-User's webpage or blog, which should contain content published by the End-User or an organization they are affiliated with. |
The End-User's preferred email address, which must conform to the RFC 5322 addr-spec syntax. | |
email_verified | True if the End-User's email address has been verified; otherwise false. Verification indicates that the email address was controlled by the End-User at the time of verification. |
gender | The End-User's gender. Common values are 'female' and 'male,' though other values may be used when appropriate. |
birthdate | The End-User's birthdate in ISO 8601-1 format (YYYY-MM-DD). The year may be omitted or represented as '0000.' |
zoneinfo | The End-User's time zone, represented as a string from the IANA Time Zone Database (e.g., Europe/Paris, America/Los_Angeles). |
locale | The End-User's locale, represented as a BCP47 language tag (e.g., en-US, fr-CA). |
phone_number | The End-User's preferred phone number, typically in E.164 format (e.g., +1 (425) 555-1212). |
phone_number_verified | True if the End-User's phone number has been verified; otherwise false. Verification indicates that the phone number was controlled by the End-User at the time of verification. |
address | The End-User's preferred postal address, represented as a JSON structure with address components (e.g., street, city, country). |
updated_at | The timestamp (in seconds since 1970-01-01) when the End-User's information was last updated. |
It's important to note that all claims are optional.
In the example provided, the payload is eyJzdWIiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDEiLCJpc3MiOiJodHRwczovL3Rlc3QuY29tIiwiYXVkIjoidGVzdCIsImV4cCI6MTc0NTUxNjUzMX0
,
which decodes to:
{ "sub": "00000000-0000-0000-0000-000000000001", "iss": "https://test.com", "aud": "test", "exp": 1745516531 }
The signature is the third and final part of a JWT. The signature is used to ensure the authenticity of the token.
The signature is created by taking the base64-url-encoded header and payload, concatenating them with a dot ('.'), and signing the resulting string using the algorithm specified in the header (in this case, RS256). The signature is then base64-url-encoded and appended to the JWT.
To verify the signature, the recipient of the token must have access to the public key of the certificate it was signed with.
These keys are typically included in the /jwks.json
endpoint.
In some cases, JWTs are signed with asymmetric algorithms. With RSA, for example.
To validate these tokens, the public keys used for signing must be explicitly made available to the client that receives the token.
In the example JWT, the signature is the last part, clN2sG9gPVlLnrb4qLpQ9tehgElkIjbRSTGBt4lTRrH7dPRJosSo1erFC_LhmyprhK66n2bhYEPxR0OHdvTxJPCBLky1FX4CYAVwiQtb1ttTH85ibr0b9dkcWFqDxzpLjc98U7zLWkCqOPfG-XIJzmuSzqPxSZlSiDyT7915LlbCDteXaKJHYxNFgWcc2YeJqEW_2qGJl5pnAkkvtl5eVbURkNR-fBUqcE24QKSY-nvOFRp8ebdzs9AMmNVa3GyIx2eRkqJosGilfXwM9rpgQjO1bKVA5PROO7TIAysYHDddV05lVvBRgWwIvXBisM9ej8lDjU0-r9vveOv5gY5GbQ
.
This signature ensures that the header and payload were indeed issued by the holder of the secret key and have not been altered in transit.
It is important to understand that although a JWT (or token) may appear to be a secure, random string of characters, by default, its contents are encoded, not encrypted. This means that the contents of tokens are readable to anyone who has access to them.
However, it is possible to encrypt or hash the contents of a token. This is called JSON Web Encryption (JWE) and is described in rfc7516:
JSON Web Encryption (JWE) represents encrypted content using JSON-based data structures. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON Web Algorithms (JWA) specification and IANA registries defined by that specification. Related digital signature and Message Authentication Code (MAC) capabilities are described in the separate JSON Web Signature (JWS) specification.
When using JWTs, do not expose sensitive information such as database IDs, social security numbers, or other data that could be abused.
To prevent data leakage, consider keeping personally identifiable information (PII) out of tokens altogether.
Instead, use a token to securely fetch this information from an endpoint such as /userinfo
.
JWTs are not inherently 'secure' by default; they are simply a vehicle for conveying data. As such, it is crucial to always verify the signature of a token before trusting its contents.
Also, JWTs are readable.
It is ill-advised to store sensitive information in them.
Instead, use the /userinfo
endpoint to obtain sensitive information or consider using JWEs (JSON Web Encryption) for to encrypt the information during transit.