5 JWT authentication best practices for Node.js apps

Mayank Choubey
Tech Tonic
7 min readMay 24, 2024

--

In this beginner article, we’ll go over the five commonly used JWT authentication best practices for Node.js applications. JWT needs no introduction, therefore let’s jump directly to the best practices.

Best practice 1 — Use secure secrets

This is obvious, but definitely needs a mention. When using JWT-based authentication in Node.js, it’s crucial to use a secure secret key to sign and verify JWTs. A secure secret key is essential to prevent unauthorized access to your application.

Why is a secure secret key important?

A secure secret key is used to sign and verify JWTs. When a user logs in, a JWT is generated and signed with the secret key. The signed JWT is then sent to the client, and on subsequent requests, the client sends the JWT back to the server. The server verifies the JWT by checking the signature, which ensures that the JWT has not been tampered with.

If an attacker gains access to the secret key, they can generate and sign their own JWTs, effectively bypassing authentication and gaining unauthorized access to your application.

Characteristics of a secure secret key

A secure secret key should have the following characteristics:

  • Long: A secure secret key should be at least 32 characters long. The longer the key, the harder it is to guess or brute-force.
  • Random: A secure secret key should be randomly generated. This ensures that the key is unpredictable and cannot be guessed.
  • Unique: A secure secret key should be unique to your application. Do not use the same secret key across multiple applications.

Generating a secure secret key

You can generate a secure secret key using a cryptographically secure pseudorandom number generator (CSPRNG). In Node.js, you can use the crypto module to generate a secure secret key:

const crypto = require('crypto');

const secretKey = crypto.randomBytes(32).toString('hex');

This code generates a 32-byte random key and encodes it as a hexadecimal string.

Using a secure secret key

When using a JWT library like jsonwebtoken, you can pass the secure secret key as an option when signing and verifying JWTs:

const jwt = require('jsonwebtoken');

const user = { id: 1, name: 'John Doe' };
const secretKey = 'your-secure-secret-key';

const token = jwt.sign(user, secretKey, { expiresIn: '1h' });

In this example, the jsonwebtoken library uses the secure secret key to sign the JWT.

Remember to store your secure secret key securely, such as using environment variables or a reliable secrets manager. Never hardcode your secret key in your code or store it in a publicly accessible location.

Best practice 2 — Use appropriate expiration times

When using JWT-based authentication in Node.js, it’s a must to set appropriate expiration times for JWTs. Expiration times determine how long a JWT remains valid, and setting appropriate expiration times can help prevent token theft and unauthorized access.

Why are appropriate expiration times important?

JWTs are self-contained tokens that contain user data and are signed with a secret key. Once a JWT is issued, it can be used by the client to access protected routes until it expires. If a JWT is stolen or compromised, an attacker can use it to access protected routes until it expires.

Setting appropriate expiration times helps limit the damage in case a JWT is stolen or compromised. For example, if an access token expires after 1 hour, an attacker can only use it to access protected routes for 1 hour, even if they obtain the token.

Types of expiration times

There are two types of expiration times you should consider:

  • Access token expiration: Access tokens should have a short expiration time, typically between 15 minutes to 1 hour. This is because access tokens are used to access protected routes and should be short-lived to minimize damage in case of token theft.
  • Refresh token expiration: Refresh tokens should have a longer expiration time, typically between 1 day to 1 week. This is because refresh tokens are used to obtain new access tokens and should be longer-lived to minimize the need for users to re-authenticate.

Setting expiration times

With a popular JWT library like jsonwebtoken, you can set the expiration time using the expiresIn option:

const jwt = require('jsonwebtoken');

const user = { id: 1, name: 'John Doe' };
const secretKey = 'your-secure-secret-key';

const accessToken = jwt.sign(user, secretKey, { expiresIn: '15m' }); // expires in 15 minutes
const refreshToken = jwt.sign(user, secretKey, { expiresIn: '1d' }); // expires in 1 day

In this example, the jsonwebtoken library uses the expiresIn option to set the expiration time for the access token and refresh token.

Remember to set appropriate expiration times based on your use case and security requirements. Shorter expiration times provide better security, but may require more frequent re-authentication.

Best practice 3 — Validate and verify tokens

Yes, this is a best practice. In short, you must verify the token. It is crucial to validate and verify tokens on each request to prevent token tampering and expiration.

Why is token validation and verification important?

JWTs are self-contained tokens that contain user data and are signed with a secret key. However, JWTs can be tampered with or expired, which can lead to unauthorized access to your application.

Validating and verifying tokens on each request ensures that:

  • The token is valid and has not been tampered with
  • The token has not expired
  • The token was issued by your application (using the correct secret key)

Token validation and verification steps

To validate and verify tokens, follow these steps:

  • Check the token format: Ensure the token is in the correct format (e.g., Bearer <token>).
  • Verify the signature: Use the secret key to verify the token’s signature.
  • Check the expiration time: Ensure the token has not expired.
  • Check the issuer: Ensure the token was issued by your application (using the correct secret key).

Using a JWT library to validate and verify tokens

When using a JWT library like jsonwebtoken, you can use the verify function to validate and verify tokens:

const jwt = require('jsonwebtoken');

const token = req.headers['x-access-token']; // assuming the token is in the x-access-token header

jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
// token is invalid or expired
} else {
// token is valid, proceed with authentication
}
});

In this example, the jsonwebtoken library verifies the token using the secret key and checks for any errors.

Again, remember to validate and verify tokens on each request to ensure the security of your application.

Best practice 4 — Use HTTPS

When using JWT-based authentication, it’s essential to use HTTPS to encrypt data in transit and prevent token interception.

Why is HTTPS important?

HTTPS ensures that data exchanged between the client and server is encrypted, making it difficult for attackers to intercept and steal JWTs. Without HTTPS, JWTs can be intercepted and stolen, allowing attackers to access protected routes and compromise user data.

How does HTTPS work?

HTTPS uses Transport Layer Security (TLS) or Secure Sockets Layer (SSL) to encrypt data in transit. When a client requests a secure connection, the server responds with its SSL/TLS certificate, which contains the public key and identity information. The client then uses this information to establish an encrypted connection.

Configuring HTTPS in Node.js

To configure HTTPS in Node.js, you need:

  • SSL/TLS certificate: Obtain an SSL/TLS certificate from a trusted certificate authority (CA).
  • Private key: Generate a private key to pair with the SSL/TLS certificate.
  • HTTPS server: Create an HTTPS server using the https module in Node.js:
const https = require('https');
const fs = require('fs');

const sslOptions = {
key: fs.readFileSync('private.key'),
cert: fs.readFileSync('certificate.crt')
};

https.createServer(sslOptions, (req, res) => {
// handle requests and responses
}).listen(443);

In this example, the https module creates an HTTPS server using the private key and SSL/TLS certificate.

Remember to use HTTPS to encrypt data in transit and prevent token interception.

Best practice 5 — Store tokens securely

The storage of tokens is important on client-side as well. After all, tokens provide access to important data. It is crucial to store tokens securely on the client-side to prevent token theft and unauthorized access.

Why is secure token storage important?

JWTs contain sensitive user data and are used to access protected routes. If tokens are stored insecurely, attackers can steal them and gain unauthorized access to your application.

Best practices for secure token storage

To store tokens securely, follow these best practices:

  • Use HttpOnly cookies: Store tokens in HttpOnly cookies, which are inaccessible to JavaScript and can only be sent to the server.
  • Use secure local storage: Use secure local storage solutions like encrypted localStorage or IndexedDB to store tokens.
  • Use a token vault: Use a token vault like a secure token storage library to manage and store tokens.
  • Avoid storing tokens in plain text: Never store tokens in plain text, as this makes them easily accessible to attackers.

Example: Using HttpOnly cookies to store tokens

You can set the secure and httpOnly options when signing tokens to store them in HttpOnly cookies:

const jwt = require('jsonwebtoken');

const token = jwt.sign(user, secretKey, {
expiresIn: '1h',
secure: true,
httpOnly: true
});

In this example, the jsonwebtoken library sets the secure and httpOnly options to store the token in an HttpOnly cookie.

Remember to store tokens securely on client side also, to prevent token theft and unauthorized access.

Thanks for reading!

--

--