DEV Community

Cover image for Can Snyk Detect JWT Security Issues?
SnykSec for Snyk

Posted on • Originally published at snyk.io

Can Snyk Detect JWT Security Issues?

JWTs are often used by developers who seek to implement authentication over traditional cookie-based sessions. However, what happens when developers misunderstand JWT security and risk a severe broken authentication vulnerability?

What is a JWT?

JSON Web Tokens (JWT) are a compact and self-contained way of securely transmitting information between parties as a JSON object. Because it is digitally signed, often using certificates, this information can be verified and trusted. JWTs are an everyday go-to for web developers when they seek to add authentication to their applications and avoid traditional browser-based cookie security and session management.

Structure of a JWT

As described above, a JWT is a regular JSON object that stores data. A JWT consists of a Header, a Payload, and a Signature. These components are encoded in Base64 and concatenated with dots (.) to form the JWT string.

  1. Header: The header typically consists of two parts: the type of token, JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.
 {
     "alg": "HS256",
     "typ": "JWT"
   }
Enter fullscreen mode Exit fullscreen mode
  1. Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. However, be careful not to store sensitive and confidential data because JWTs are often not encrypted and can be decoded by anyone to access the data. There are three types of claims: registered, public, and private claims.
{
     "sub": "1234567890",
     "name": "John Doe",
     "admin": true
   }
Enter fullscreen mode Exit fullscreen mode
  1. Signature: To create the signature part, you must take the encoded header, the encoded payload, a secret, and the algorithm specified in the header and sign them.
 HMACSHA256(
     base64UrlEncode(header) + "." +
     base64UrlEncode(payload),
     secret)
Enter fullscreen mode Exit fullscreen mode

The resulting JWT might look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

The role of JWT in API and microservices

JWTs play an important role in frontend and backend developers as they build out authentication and build APIs and microservices. Unlike traditional session-based authentication on the server, which requires the server to maintain a persistent session state through a database, JWTs allow for a more scalable and efficient approach due to their stateless nature. When a user logs in, the server generates a token that encodes user information and signs it with a secret key. This token is then sent to the client, which includes it in the header of subsequent requests.

The server can verify the token's signature to authenticate the user without storing session data. JWTs' stateless nature makes them particularly well-suited for distributed systems and microservices architectures, where maintaining a centralized session store could become a bottleneck or a single point of failure.

Popular open source JWT npm packages for Node.js

When working with JWT in Node.js, developers have a variety of open source libraries at their disposal, but they often pick the popular jsonwebtoken npm package. These libraries simplify the process of generating, signing, and verifying tokens. However, understanding the differences in features and security considerations is pivotal to avoiding misusing the library in an insecure way that would introduce a security liability.

Other JSON npm packages include node-jose and web framework integrations like passport-jwt and @fastify/jwt.

JWT security risks in decoding a token

To demonstrate a classic mistake in insecurely using JWTs to manage authentication, we’ll pick the popular options that most developers will be drawn to: Express web framework, and jsonwebtoken as the JWT library.

Can you find the security vulnerability in the following Node.js code?:

const express = require('express');
const jsonWebToken = require('jsonwebtoken');
require('dotenv').config();

const webApp = express();

const securityKey = process.env.JWT_SECRET;

const validateAccess = (request, response, next) => {
  const authorizationHeader = request.headers.authorization;

  if (!authorizationHeader) {
    return response.status(401).json({ error: 'Missing authentication credentials' });
  }

  try {
    const decodedToken = jsonWebToken.decode(authorizationHeader,{complete: true});
    request.userContext = decodedToken;
    next();
  } catch (authError) {
    return response.status(403).json({ error: 'Invalid or expired token' });
  }
};

webApp.get('/privileged-area', validateAccess, (request, response) => {
  const { username } = request.userContext;
  if (username === 'admin') {
    // Access to sensitive data is now available.
    return response.status(200).json({ status: 'Access permitted to admin' });
  }
  response.status(403).json({ status: 'Insufficient permissions' });
});

webApp.get('/issue-token', (request, response) => {
  const userClaims = { username: 'user' };
  const authenticationToken = jsonWebToken.sign(userClaims, securityKey, { expiresIn: '1h' });
  response.status(200).json({ authenticationToken });
});

webApp.listen(3001, () => {
  console.log('Application listening on port 3001');
});
Enter fullscreen mode Exit fullscreen mode

This Node.js code uses jsonWebToken.decode() to decode the JWT. This method only decodes the token without verifying its signature. So, while it works because the decodedToken returns data, there’s no guarantee that this data was the same that the server set and that it wasn’t tampered with by a third-party actor. This means that anyone can tamper with the token, and the application will still accept it as valid. Instead, use jsonWebToken.verify() to ensure the token is decoded and verified against the secret key.

Snyk Detects JWT Security Issues

Run the above vulnerable Node.js code through Snyk. It will pick up on the JWT security issue that uses the insecure JSON web token method jwt.decode() in the jsonwebtoken library:


Depending on where you integrate with Snyk, it will pick up on it. I imported the GitHub code repository via the Snyk web app in the above screenshot. Once imported, Snyk will scan the repository for manifest files such as Python’s requirements.txt or npm’s package.json, for example, to detect vulnerable dependencies. Snyk will also detect IaC files like Terraform and perform a static analysis code that scans the code itself.

As you can see in the above screenshot, Snyk detected the misuse of library code in jsonwebtoken, which uses the insecure jwt.decode function and can lead to broken authentication.

The following is a recommended way to decode a JWT securely:

   try {
     const decodedToken = jsonWebToken.verify(authorizationHeader, securityKey);
     request.userContext = decodedToken;
     next();
   } catch (authError) {
     return response.status(403).json({ error: 'Invalid or expired token' });
   }
Enter fullscreen mode Exit fullscreen mode

Other JWT security issues in the example Node.js code above include the following gaps:

  • Hardcoded sensitive data: The code snippet stores the secret key using environment variables. Ensure these environment variables are securely managed and not exposed in version control systems and that you can rotate and securely manage them.
  • Insufficient logging: Implement logging for authentication attempts and errors. This can help monitor and respond to potential security incidents.
  • Rate limiting: How do you control brute force login attempts and other abuse of authentication APIs? Implement rate limiting to avoid resource hogging and unmitigated authentication attempts.
  • Token expiration: While the code sets an expiration time for tokens, enforcing this by verifying the token's expiration during each request is essential.

Recommended JWT Security Best Practices

Securing a REST API with JWT involves more than just issuing and verifying tokens. It requires careful consideration of several security practices to mitigate potential vulnerabilities. For instance, always use a strong secret key for signing tokens, preferably asymmetric keys, and ensure that tokens are transmitted over HTTPS to prevent interception.

Additionally, it's essential to implement proper token expiration and revocation strategies. Tokens should have a short lifespan to minimize the risk of misuse if compromised. Refresh tokens can be used to issue new access tokens without requiring the user to log in again, but they should also be managed securely.

You can refer to a more comprehensive guide on Securing REST APIs.

Top comments (0)