Blog

JWT validation: how-to and best libraries to use

Learn about JSON Web Token (JWT) validation, why it’s important, what the best practices are, and how to do it using trusted third-party libraries.


JWT, which stands for JSON Web Token and is pronounced “jot”, is an open standard for securely sharing JSON data between parties. JWTs are widely adopted in modern web development and used in authentication and authorization workflows.

However, JWTs must be handled carefully if we want our apps and users to stay safe. We need to ensure that the JWT can be trusted and hasn’t been tampered with and validate several token elements.

In this article, we will discuss JWT validation and some of the best libraries you can use. But first, let’s review the basics of JWTs.

JWT 101

A JSON Web Token (JWT) is an open standard for securely transmitting information between different systems as signed JSON objects. JWTs are commonly used in the OAuth 2.0 and OpenID Connect (OIDC) protocols.

A JWT consists of 3 parts:

  • Header: Specifies the token type (JWT) and the signing algorithm used to create the token’s signature (like HMAC SHA256 or RSA).
  • Payload: Contains the claims – pieces of information about the user. There are standard claims (which are registered with the Internet Assigned Numbers Authority (IANA) and defined by the JWT specification) and custom ones (which are used to represent the information we want our JWT to contain, e.g., the user’s email or profile picture). Some examples are:
    • sub: Standard claim that specifies the subject of the JWT, for example, a user ID.
    • iss: Standard claim that specifies the issuer of the JWT.
    • aud: Standard claim that specifies the recipient for which the JWT is intended.
    • exp: Standard claim that specifies the time after which the JWT expires.
    • email: A custom claim that contains the user’s email.
    • picture: A custom claim that contains a link to the user’s profile picture.
  • Signature: A signature is used to verify that the sender of the JWT is who they say they are and to ensure that the message wasn't tampered with along the way.

Each part is base64url-encoded and concatenated using dots: encoded_header.encoded_payload.encoded_signature.

An example JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJlbWFpbCI6ImhncmFuZ2VyQGhvZ3dhcnRzLmV4YW1wbGUiLCJhZG1pbiI6InRydWUiLCJpYXQiOjE3MDMyNjc0NjgsImlzcyI6InlvdXItc2Fzcy1hcHAiLCJleHAiOjE3MDM2OTk0Njh9._PSZ4_OdZnp7Rcg35ocnTq9ol7-y_LHl7DvnTPBLKjU

If we decode this JWT, we will get its individual parts.

For more information on JWTs, check out JSON Web Tokens.

Protecting JWTs: JWS, JWE, and JWK

JWTs can be protected via JSON Web Signature (JWS) or JSON Web Encryption (JWE).

  • JWS uses a signature algorithm and is used to share data between parties when confidentiality is not required. This is because claims within a JWS can be read as they are simply base64-encoded (but carry with them a signature for authentication). JWS supports symmetric key-based MACs (single key used to sign and verify) and asymmetric key-based digital signatures (private key used to sign, public key used to verify).
  • JWE uses a signature and encryption algorithm and guarantees confidentiality (i.e. ensures that only the receiver can decrypt data). Claims in a JWE, are encrypted and entirely opaque to clients using them as their means of authentication and authorization. JWE supports both symmetric key cryptography (single key used to encrypt and decrypt) and asymmetric key cryptography (public key used to encrypt, private key used to decrypt).

Both JWE and JWS operations expect a JSON Web Key (JWK). A JWK is a JSON data structure that represents a cryptographic key. Using a JWK rather than one or more parameters allows for a generalized key as input that can be applied to a number of different algorithms that may expect a different number of inputs.

Another term you might come around while working with JWTs is JWA: JSON Web Algorithms. JWA defines the cryptographic algorithms that JWE encryption and JWS signing can use. Some examples are:

  • HS256: HMAC using SHA-256
  • RS256: RSASSA-PKCS1-v1_5 using SHA-256
  • A256GCM: AES GCM using 256-bit key

How to validate a JWT

Every time your app receives a JWT, you must validate it. This is the number one best practice to have in mind. You should never implicitly trust a JWT, even if you operate within an internal network without internet access.

You can use any of the following ways to validate a JWT:

  • Use a middleware for your web framework.
  • Use a third-party library.
  • Manually implement the checks described in the JWT spec.

Verifying and validating a JWT includes the following steps.

  • Parse the JWT to extract the header, payload, and signature.
  • Verify the signature using the secret key or public key.
  • Check the expiration time (exp) and the not-before time (nbf) claims to ensure the JWT is valid.
  • Verify the issuer (iss) claim to ensure the JWT was issued by a trusted party.
  • Verify the audience (aud) claim to ensure the JWT is intended for the correct recipient.

Third-party libraries for JWT validation

We strongly recommend using middleware for your web framework or a third-party library for JWT validation instead of rolling out your own custom implementation. Avoid implementing JWT from scratch, as cryptography and security are very complex topics—even for experienced developers.

In this section, we list our favorite libraries for some of the SDKs we support. If you don’t want to use one of these, you can refer to jwt.io for an extensive list of libraries for token signing and verification for various languages.

Remember to follow the library’s instructions, check out for security notices, change default settings where applicable, and refer to the library’s documentation for the latest code samples.

Validate JWTs with Node

For all things JavaScript, the jose library is great and widely used. It supports JWTs, JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and JSON Web Key Set (JWKS) and has zero dependencies.

This is an example of validating a token using a public JSON Web Key Set hosted on a remote URL.

	
const jose = require('jose');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const JWKS = jose.createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'))

const { payload, protectedHeader } = await jose.jwtVerify(jwt, JWKS, {
  issuer: 'urn:example:issuer',
  audience: 'urn:example:audience',
})
	

For more examples, refer to the jose repo readme.

For an example of validating a token using AuthKit and Next.js, see this repo.

Validate JWTs with Golang

jwt-go is a great Golang implementation of the JWT spec. To install the package, run go get -u github.com/golang-jwt/jwt/v5.

Let’s see an example of parsing and validating a token using the HMAC signing method.

	
import "github.com/golang-jwt/jwt/v5"

tokenString:= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU"

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    // Don't forget to validate the alg is what you expect:
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        // Note that if you use RSA the expected return type is rsa.PublicKey, not string
        // see https://github.com/dgrijalva/jwt-go/issues/438
    }

    // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
    return hmacSampleSecret, nil
})
if err != nil {
    log.Fatal(err)
}

if claims, ok := token.Claims.(jwt.MapClaims); ok {
    fmt.Println(claims["foo"], claims["nbf"])
} else {
    fmt.Println(err)
}
	

You can also use certain validators to check specific claims of the JWT. For example, WithIssuer(ISSUER) will check if the JWT’s issuer matches the input argument ISSUER.

For more details and examples, check the jwt-go docs.

Validate JWTs with Python

PyJWT is a popular Python implementation of the JWT spec. To install the package run pip install PyJWT.

To verify a token call the decode method passing the JWT, the signing key, and the algorithm.

	
import jwt

private_key = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..."
public_key = b"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEAC..."
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg"

try:
    decoded_token = jwt.decode(token, public_key, algorithms=['RS256'], options={"verify_exp": True, "verify_iss": True, "verify_aud": True})
    print(decoded_token)
except jwt.ExpiredSignatureError:
    print("Token has expired")
except jwt.InvalidIssuerError:
    print("Invalid issuer")
except jwt.InvalidAudienceError:
    print("Invalid audience")
except jwt.InvalidTokenError:
    print("Invalid token")
	

decode will verify the JWT signature and return the token claims. Alternatively, you can use decode_complete which returns a dictionary containing the token header (JOSE Header), the token payload (JWT Payload), and token signature (JWT Signature) on the keys “header”, “payload”, and “signature” respectively.

For more details and examples, check the PyJWT docs.

Validate JWTs with Ruby

JWT is a popular Ruby implementation of the JWT spec. To install the gem run gem install jwt.

To verify a token call the JWT.decode method, passing the algorithm used in the options hash. In this example we are using RS256.

  
rsa_private = OpenSSL::PKey::RSA.generate(2048)
rsa_public = rsa_private.public_key

payload = { data: 'test' }

token = JWT.encode(payload, rsa_private, 'RS256')

# eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ
puts token

decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })

# Array
# [
#   {"data"=>"test"}, # payload
#   {"alg"=>"RS256"} # header
# ]
puts decoded_token
  

Validate JWTs with PHP

PHP-JWT is a popular PHP implementation of the JWT spec. To download the package run composer require firebase/php-jwt.

To verify a token use the JWT::decode method and then proceed to verifying the claims.

	
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$privateKey = <<<EOD-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAuzWHN...EOD;
$publicKey = <<<EOD-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BA...EOD;

$payload = [
    'iss' => 'example.org',
    'aud' => 'example.com',
    'iat' => 1356999524,
    'nbf' => 1357000000
];

$jwt = JWT::encode($payload, $privateKey, 'RS256');
echo "Encode:\n" . print_r($jwt, true) . "\n";

$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));

/*
 NOTE: This will now be an object instead of an associative array. To get
 an associative array, you will need to cast it as such:
*/

$decoded_array = (array) $decoded;
	

Validate JWTs with Java

The jose.4.j library is an open-source implementation of JWT and the JOSE specification suite.

You can verify the signature using verifySignature. Let’s see an example of signature verification using JSON Web Signature (JWS).

	
// The complete JWS representation, or compact serialization, is string consisting of
// three dot ('.') separated base64url-encoded parts in the form Header.Payload.Signature
String compactSerialization = "eyJhbGciOiJFUzI1NiJ9." +
        "VGhpcyBpcyBzb21lIHRleHQgdGhhdCBpcyB0byBiZSBzaWduZWQu." +
        "GHiNd8EgKa-2A4yJLHyLCqlwoSxwqv2rzGrvUTxczTYDBeUHUwQRB3P0dp_DALL0jQIDz2vQAT_cnWTIW98W_A";

// Create a new JsonWebSignature
JsonWebSignature jws = new JsonWebSignature();

// Set the algorithm constraints based on what is agreed upon or expected from the sender
jws.setAlgorithmConstraints(new AlgorithmConstraints(ConstraintType.PERMIT, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256));

// Set the compact serialization on the JWS
jws.setCompactSerialization(compactSerialization);

// Set the verification key
// Note that your application will need to determine where/how to get the key
// Here we use an example from the JWS spec
PublicKey publicKey = ExampleEcKeysFromJws.PUBLIC_256;
jws.setKey(publicKey);

// Check the signature
boolean signatureVerified = jws.verifySignature();

// Do something useful with the result of signature verification
System.out.println("JWS Signature is valid: " + signatureVerified);

// Get the payload, or signed content, from the JWS
String payload = jws.getPayload();

// Do something useful with the content
System.out.println("JWS payload: " + payload);
	

To validate and process a JWT, you must use JwtConsumerBuilder to construct an appropriate JwtConsumer.

	
// Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
// be used to validate and process the JWT.
// The specific validation requirements for a JWT are context dependent, however,
// it is typically advisable to require a (reasonable) expiration time, a trusted issuer, and
// an audience that identifies your system as the intended recipient.
// If the JWT is encrypted too, you need only provide a decryption key or
// decryption key resolver to the builder.
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
        .setRequireExpirationTime() // the JWT must have an expiration time
        .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
        .setRequireSubject() // the JWT must have a subject claim
        .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by
        .setExpectedAudience("Audience") // to whom the JWT is intended for
        .setVerificationKey(rsaJsonWebKey.getKey()) // verify the signature with the public key
        .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
        .build(); // create the JwtConsumer instance

try
{
    //  Validate the JWT and process it to the Claims
    JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
    System.out.println("JWT validation succeeded! " + jwtClaims);
}
catch (InvalidJwtException e)
{
    // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
    // Hopefully with meaningful explanations(s) about what went wrong.
    System.out.println("Invalid JWT! " + e);

    // Programmatic access to (some) specific reasons for JWT invalidity is also possible
    // should you want different error handling behavior for certain conditions.

    // Whether or not the JWT has expired being one common reason for invalidity
    if (e.hasExpired())
    {
        System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
    }

    // Or maybe the audience was invalid
    if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
    {
        System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
    }
}
	

For more details and code examples, see the jose.4.j docs.

Validate JWTs with .NET

IdentityModel Extensions for .NET is Microsoft’s library for JWTs. The validation steps are captured into Validators, which are all in one source file. The library has the following validators:

  • ValidateAudience: Ensures that the token is indeed for the application that validates the token.
  • ValidateIssuer: Ensures that the token was issued by a STS I trust.
  • ValidateIssuerSigningKey: Ensures the application validating the token trusts the key that was used to sign the token.
  • ValidateLifetime: Ensures that the token has not expired.
  • ValidateTokenReplay: Ensure the token is not replayed (this is a special case for some onetime use protocols).

Validators can be turned off, but this is not recommended.

Let's see an example of JWT validation using this library.

	
var jsonWebTokenHandler = new JsonWebTokenHandler();
var tokenValidationResult = jsonWebTokenHandler.ValidateToken(token, validationParameters);
if (!tokenValidationResult.IsValid)
{
   // Handle each exception which tokenValidationResult can contain as appropriate for your service
   // Your service might need to respond with a http response instead of an exception.
   if (tokenValidationResult.Exception != null)
       throw tokenValidationResult.Exception;
    
   throw CustomException("");
}
	

Best practices for JWTs

No matter how you choose to handle your JWTs, make sure to follow these best practices to keep your users safe.

  • Keep the secret key safe: Keep the secret key used to sign the JWT confidential. Use a key management system or store the key in a secure environment variable.
  • Always validate JWTs: Always validate a JWT before using it.some text
    • Verify the signature using the secret or public keys to confirm the token’s authenticity.
    • Validate the algorithm that was used.
    • Check the expiration time (exp) and the not-before time (nbf) claims to ensure the JWT is valid.
    • Verify the issuer (iss) claim to ensure the JWT was issued by a trusted party.
    • Verify the audience (aud) claim to ensure the JWT is intended for the correct recipient.
  • Keep payloads small: Avoid storing sensitive information in the payload, and consider limiting the number of claims to only essential data.
  • Keep JWTs secret: JWTs are signed JSON objects that contain user information. They can include sensitive data such as user IDs, roles, and permissions. If an attacker gets hold of a JWT, they can use it to impersonate the user and gain unauthorized access to your application. Therefore, it's crucial to keep JWTs secret and ensure that only authorized parties can access them. To keep JWTs safe, ensure they are transmitted securely over the network using HTTPS and store them securely on the client side.
  • Use secure libraries: Utilize well-established libraries and frameworks for creating, parsing, and verifying JWTs. Avoid implementing JWT from scratch, as cryptography and security are very complex topics—even for experienced developers.
  • Use strong encryption and algorithms: Choose robust asymmetric algorithms (like RSA or ECDSA) for signing JWTs. They are generally considered the best, as they use a public/private key pair, making it difficult for an attacker to forge tokens. The most widely used option, supported by most technology stacks, is RS256. If you really need to use symmetric keys, then choose HS256 (HMAC using SHA-256) — though using symmetric keys is not recommended.
  • Use short lifetimes for tokens: Have your JWTs expire after a short period of time to reduce the window of opportunity for misuse in case a token is compromised. To not sacrifice UX, implement session management with refresh tokens that live longer. When you generate JWTs make sure to set a short expiration time.
In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.