JSON Web Token guide: How to validate a JWT
Learn how to validate a JWT with this comprehensive guide. Understand JSON Web token structure and validation through practical code examples.
- Quickstart
- 15 min
Validating a JSON Web Token (JWT) involves verifying the signature. It should belong to the correct public key which will ensure the authenticity of the information. Also it must not be expired and must be sent to the correct recipients.
Understanding JWT token concept
JSON Web Tokens (JWTs) are a widely-used standard for securely transmitting information between parties in web applications. Let's dive into the main concepts and components of JWTs, especially focusing on the keywords you provided.
A JWT is a compact, URL-safe token often used in web applications to authorize access to resources, like APIs. It is a JSON object. It encodes data in JSON format and consists of three parts:
- Header: Specifies the token type (
JWT
) and the signing algorithm (e.g.,HS256
for HMAC SHA-256). - Payload (or claims): Contains information about the user or system, known as JWT claims.
- Signature: Validates the integrity of the token, ensuring it hasn't been altered in transit.
JWT Header and JSON Web Signature (JWS)
The JWT header often specifies a signing algorithm (e.g., RS256
, HS256
), which is used to generate the JWT signature. This signature is part of the JSON Web Signature (JWS) standard and can be generated using either symmetric keys (shared secret) or asymmetric keys (public/private key pair).
The payload of JSON Web Token: the JWT Claims
JWT claims in the payload store information such as:
- sub (subject): Identifies the user or entity.
- iss (issuer): Specifies the token issuer (e.g., the authorization server).
- aud (audience): Indicates the intended recipient of the token.
- exp (expiration): Defines when the token expires.
Here is an example of JWT payload:
{
"aud": [],
"client_id": "d1f1fc75-51a3-4c07-8ba6-698521728290",
"email": "alexandre@cryptr.co",
"env": "sandbox",
"exp": 1709330437,
"iat": 1709294437,
"jti": "0c46d26c-9974-4ee4-96d3-081990e643f9",
"jtt": "access",
"org": "muffun-qy6S9EYuXgtcTeG3YiYwX4",
"scope": ["openid", "email", "profile"],
"sub": "cryptr|41a55520-645d-49eb-a0e8-2e95fde8f56c",
"ver": 3
}
Claims provide flexibility, enabling JWTs to store custom data as well as standard claim names. JWT claims can also be used to reject tokens that don't match specific criteria.
Summary: JWT Security Best Practices
To securely use JWTs, always:
- Validate JWTs before trusting the information they contain.
- Check the signature to ensure it’s valid.
- Confirm the token issuer and audience claims match expected values.
- Reject tokens if they fail any validation criteria.
Cryptr creates signed tokens with asymmetric keys and verifying signatures through a JWKS endpoint provides robust security. In web APIs, a JWT can decode data in transit while ensuring the signature is valid and belongs to the expected issuer, making it a reliable choice for access control and authentication.
These Tokens can be decoded using online tools like JWT.io or using libraries specific to each language. However, decoding is quite simple since it can be done using a simple Base64 Decode.
Now that you have seen this you may be wondering how can we ensure that the Token is not a Token altered by a malicious person? All you need to do is verify your signature. Indeed, JWT Tokens have a signature mechanism. Each Token is signed by Cryptr when issued via a private Key. For your part, you can easily find your public Key using the information contained in the decoded Cryptr Token. Once your public Key has been retrieved, you can then verify the signature of your Token. This is what we will see in more detail in this guide.
- Retrieve User Tokens
- Get the Token you need
- Decode the Token
- Retrieve the public Key
- Verify Signature
1. Retrieve User's JWTs, Access token and ID token in payload
To start you will have to retrieve your User's Tokens. Most of the time after an Authentication Challenge you will receive a code via the HTTP request parameters (from our service to your redirection URL).
{your-redirection-url}?code=authorization_code&request_id=request_id
By using this code you will be able to retrieve all the information you need (ID Token, Access Token, Expiration, etc.). To do this you need to make an API request to our services using this code as a parameter as well as specifying the grant_type authorization_code
.
For example the query would look like:
We first need to get the Key ID (kid) of the JWT to find the associated public Key. You can get the kid from the Token header (the first part before the first dot) with a simple base64 decode.
curl -X POST '${cryptr_service_url}/oauth/token' \
-d grant_type="authorization_code" \
-d code="9xO5oCjbwHHeIPu8QId3325AAmGjZ76vjD5WA49…"
You should then receive a payload containing what you need next:
{
"access_token": "eyJhbGciO…",
"expires_in": 36000,
"id_token": "eyJhbGciO…",
"scope": [
"openid",
"email",
"profile"
],
"token_type": "Bearer"
}
Depending on your needs, you can then retrieve either the Access Token, the ID Token or both. As it stands, it is of little use to you since it is encoded and therefore impossible for a human to understand. So you will have to decode it.
2. Decode the JWT claims
Once your token has been recovered, you must decode it to be able to access the information it contains in a readable manner. Most languages have libraries or native functions that will allow this token to be decoded, which we remind you is in JWT format.
- JavaScript
- Ruby
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const token = "YOUR_JWT_TOKEN";
// Decodes without verification
const decoded = jwt.decode(token,
// Setting { complete: true } allows you to access both the header and the payload.
{ complete: true }
);
// You will get the kid (key if of the public key) and the iss (issuer URL)
// usefull for the next step
console.log("Header:", decoded.header);
// {
// "alg": "RS256",
// "iss": "https://auth.cryptr.dev/t/muffun-qy6S9EYuXgtcTeG3YiYwX4",
// "kid": "cbf227bd-1427-44f0-8581-20199f0a8ebb",
// "typ":"JWT"
// }
# gem install jwt
require 'jwt'
# Example JWT token
token = "YOUR_JWT_TOKEN"
# Decode the token without verifying the signature
decoded_token = JWT.decode(token, nil, false)
# The decoded_token array contains [payload, header]
payload = decoded_token[0]
header = decoded_token[1]
# You will get the kid (key if of the public key) and the iss (issuer URL)
# usefull for the next step
puts "Header: #{header}"
puts "Payload: #{payload}"
# {
# "alg": "RS256",
# "iss": "https://auth.cryptr.dev/t/muffun-qy6S9EYuXgtcTeG3YiYwX4",
# "kid": "cbf227bd-1427-44f0-8581-20199f0a8ebb",
# "typ":"JWT"
# }
It is important to know that when a resource server receives a JWT in an authorization request, it needs to validate the token. This is done to ensure it's legitimate and hasn’t been tampered with. JWT validation typically includes:
- Validating the signature: The server verifies the token’s signature using the expected key. For asymmetric keys, the server uses a public JSON Web Key from the JSON Web Key Set (JWKS) endpoint.
- Validating the token issuer: It checks the issuer claim (
iss
) to confirm that the token belongs to the expected authorization server. - Validating the token claims: It verifies the audience (
aud
), expiration (exp
), and other claims to ensure the token is valid for the intended resource.
3. Retrieve the public key in JWKS (JSON Web Key Set) from the authorization server
Once you have decoded your Token you should have a header similar to this:
{
"alg": "RS256",
"iss": "https://auth.cryptr.dev/t/muffun-qy6S9EYuXgtcTeG3YiYwX4",
"kid": "cbf227bd-1427-44f0-8581-20199f0a8ebb",
"typ":"JWT"
}
To retrieve your public key you will be able to use the kid contained in the header of your JWT. Once your kid has been retrieved you will have to use the following endpoint:
{your-cryptr-service-url}/t/{your_org_domain}/.well-known
To make it simpler, you can also simply retrieve the ISS contained in the header of your token and add the /.well-known route.
If you prefer this method always check that the domain is https://{your-domain}.authent.me
for dedicated instance or https://cryptr.eu
(can also be .us
or .asia
)
- JavaScript
- Ruby
// npm install jwks-rsa
const jwksClient = require('jwks-rsa');
// Initialize the JWKS client
const client = jwksClient({
jwksUri: 'https://YOUR_CRYPTR_BASE_URL/.well-known/jwks.json',
});
// Function to get the public key from the JWKS
const getKey = (header, callback) => {
client.getSigningKey(header.kid, (err, key) => {
if (err) return callback(err);
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
require 'net/http'
require 'uri'
require 'json'
# Configure the JWKS URL from the authorization provider
JWKS_URL = 'https://YOUR_AUTH_PROVIDER/.well-known/jwks.json'
# Method to fetch public keys from the JWKS
def fetch_jwks_keys
uri = URI(JWKS_URL)
response = Net::HTTP.get(uri)
JSON.parse(response)['keys']
end
# Method to find the corresponding key in the JWKS
def find_key(kid, jwks_keys)
jwks_keys.find { |key| key['kid'] == kid }
end
4. Validate token: verify the JWT signature
When performing manual JWT validation, it's important to:
- Verify the token's signature to confirm that it was issued by a trusted source.
- Ensure that the
iss
(issuer) claim in the JWT corresponds to the token's issuer. If theiss
claim does not match the expected issuer, reject the token. - Verify the
aud
(audience) claim to confirm the token is intended for different audiences and is authorized for the application. If it doesn’t match, reject the token. - Confirm that the token has not expired , check the expiration (
exp
) claim to ensure that the token isn’t expired. If the token has expired, it will fail validation.
JWT middleware can also simplify validation in web frameworks, handling token decoding and verification as part of the HTTP request lifecycle.
- JavaScript
- Ruby
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
// Verify the JWT
const validateJwt = (token) => {
jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
if (err) {
console.error("Token validation failed:", err);
return;
}
console.log("Token is valid:", decoded);
});
}
// Example usage
const token = "YOUR_JWT_TOKEN";
validateJwt(token);
# gem install jwt
# gem install json-jwt
require 'jwt'
require 'json/jwt'
# JWT validation method
def validate_jwt(token)
jwks_keys = fetch_jwks_keys
decoded_header = JWT.decode(token, nil, false).first
kid = decoded_header['kid']
# Retrieve the matching key
jwk_data = find_key(kid, jwks_keys)
raise "Key not found for KID: #{kid}" if jwk_data.nil?
# Use the public key to verify the JWT
jwk = JSON::JWK.new(jwk_data)
public_key = jwk.to_key
begin
decoded_token = JWT.decode(token, public_key, true, { algorithm: jwk_data['alg'] })
puts "The token is valid: #{decoded_token}"
rescue JWT::DecodeError => e
puts "Token validation error: #{e.message}"
end
end
# Using the method with an example token
token = "YOUR_JWT_TOKEN"
validate_jwt(token)
On this .well-known
endpoint you can retrieve the public key corresponding to your kid. Once the public key (also called JWK) has been found, simply retrieve it and in most cases use it as is (in full) in JWT libraries to verify the signature of your Access / ID Token. Some libraries also accept passing the well-known URL directly. Be careful to check the specifics of each library for your language.
Depending on the library used, you may have to check the correspondence of your signature yourself. If this is the case, you will simply have to compare the signature contained in your JWT with the one you obtained. If the two character strings are identical, congratulations, you have just proven the authenticity of your JWT.
I can trust the presence of the Token information, so I can validate:
- temporality
- exhalation,
- the issuer: it comes from my Cryptr authorization service
- and that as an application I am the right recipient (client_id), the right audience
To conclude here is some tips about how to use your Tokens.
User identity:
- in browser the id_token must be the only way to authenticate an user
- on the server side, the user_info from an access_token, should be used because we never send the id_token in Client / Server exchanges
We hope that this short guide has helped you better understand JWTs and above all that it has helped you to decode, use and verify the authenticity of the tokens that we send you.