Skip to content

JSON Web Tokens (JWT)

1. Introduction

JSON Web Tokens (JWTs) are a compact, self-contained way of securely transmitting information between parties as a JSON object. JWTs are widely used for authentication and authorization in REST APIs, providing a secure, efficient, and scalable way to manage user sessions without relying on server-side state. JWTs contain encoded information that can be verified and trusted because they are digitally signed. This chapter explores JWTs, their structure, use cases, benefits, and best practices for implementing JWT-based authentication in REST APIs. JSON Web Tokens (JWTs) are a compact, self-contained way of securely transmitting information between parties as a JSON object. JWTs are widely used for authentication and authorization in REST APIs, providing a secure, efficient, and scalable way to manage user sessions without relying on server-side state. JWTs contain encoded information that can be verified and trusted because they are digitally signed. This chapter explores JWTs, their structure, use cases, benefits, and best practices for implementing JWT-based authentication in REST APIs.

2. What is a JWT?

A JSON Web Token (JWT) is a string that represents a set of claims encoded as a JSON object. JWTs are used to transmit information between a client and a server securely and efficiently. They are typically used for authentication purposes, allowing APIs to verify user identity and manage access without maintaining session state on the server.

Structure of a JWT:

A JWT consists of three parts: Header, Payload, and Signature, separated by dots (.). Each part is Base64URL encoded.

  1. Header: Contains metadata about the token, including the signing algorithm used.

    • Example:
      {
      "alg": "HS256",
      "typ": "JWT"
      }
  2. Payload: Contains claims, which are statements about an entity (typically the user) and additional data. Claims can be standard (like iss, exp, sub) or custom claims defined by the application.

    • Example:
      {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true,
      "iat": 1516239022
      }
  3. Signature: Used to verify the integrity of the token. It is created by signing the encoded header and payload with a secret key using the specified algorithm.

    • Signature Creation:
      HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)

Full JWT Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

3. Use Cases for JWTs

  1. Authentication

    • JWTs are commonly used to verify user identity. After successful login, the server issues a JWT, which the client stores and sends with each subsequent request to access protected routes.

    • Example: A user logs into an application, receives a JWT, and uses it to authenticate subsequent requests to the API.

  2. Authorization

    • JWTs can include user roles and permissions within the payload, allowing the server to control access based on the claims contained in the token.

    • Example: A JWT includes a claim indicating the user’s role as an admin, granting access to administrative functions of the API.

  3. Stateless Sessions

    • JWTs enable stateless authentication, meaning no session data needs to be stored on the server. This reduces server load and simplifies horizontal scaling, making JWTs ideal for distributed systems.

    • Example: An e-commerce site uses JWTs to manage user sessions without relying on server-side session storage.

  4. Secure API-to-API Communication

    • JWTs are also used to secure communication between microservices or APIs, providing a way to verify the identity and permissions of each service.

    • Example: A payment processing service verifies incoming requests from a front-end API using JWTs, ensuring that only authorized requests are processed.

4. Implementing JWT Authentication in REST APIs with Fastify

Fastify, a fast and low-overhead web framework for NodeJS, provides easy integration with JWT authentication through plugins. Below is an example of how to set up JWT-based authentication in Fastify.

Example: JWT Authentication with Fastify

const fastify = require("fastify")({ logger: true });
const jwt = require("fastify-jwt");
// Register the JWT plugin with a secret key
fastify.register(jwt, {
secret: "supersecretkey", // Use a secure, unpredictable key in production
});
// Route to authenticate a user and issue a JWT
fastify.post("/login", async (request, reply) => {
const { username, password } = request.body;
// Mock user authentication (replace with actual logic)
if (username === "user" && password === "password") {
// Create a JWT with the user's information
const token = fastify.jwt.sign(
{ username, role: "user" },
{ expiresIn: "1h" }
);
return { token };
}
reply.code(401).send({ error: "Invalid credentials" });
});
// Middleware to verify JWTs for protected routes
fastify.addHook("onRequest", async (request, reply) => {
try {
await request.jwtVerify(); // Verifies the token and attaches decoded data to the request
} catch (err) {
reply.code(401).send({ error: "Unauthorized" });
}
});
// Protected route example
fastify.get("/protected", async (request, reply) => {
return {
message: `Hello, ${request.user.username}! You have access to this protected route.`,
};
});
// Start the Fastify server
fastify.listen({ port: 3000 }, (err, address) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.log.info(`Server running at ${address}`);
});

Key Points in the Example:

  • JWT Creation: Upon successful login, a JWT is created using fastify.jwt.sign(), which includes the user’s information and an expiration time.
  • JWT Verification: The onRequest hook uses request.jwtVerify() to validate the token on protected routes, ensuring that only authenticated users can access them.
  • Stateless Authentication: No session data is stored on the server, and the client is responsible for including the JWT in each request.

5. Benefits of Using JWTs

  1. Stateless and Scalable

    • JWTs eliminate the need for server-side session storage, making them ideal for scalable and distributed systems where maintaining session state would be cumbersome.
  2. Secure and Self-Contained

    • JWTs are self-contained, meaning they carry all the information needed for authentication and authorization within the token itself. This reduces the need for multiple database lookups.
  3. Easy to Implement and Use

    • JWTs are easy to integrate into APIs, with many libraries and plugins available for common frameworks, reducing the complexity of implementing authentication.
  4. Supports Custom Claims

    • JWTs can include custom claims, such as user roles, permissions, or other metadata, allowing fine-grained control over access to resources.
  5. Interoperable Across Platforms

    • JWTs are platform-independent and can be used across different programming languages, making them a flexible solution for securing APIs and microservices.
  6. Short-Lived Tokens with Refresh Capabilities

    • JWTs can be configured to expire after a short duration, reducing the impact of compromised tokens. Refresh tokens can be used to issue new access tokens without requiring the user to re-authenticate.

6. Best Practices for Using JWTs

  1. Use Strong, Unique Secrets

    • Always use strong, unpredictable secrets for signing JWTs. Avoid hard-coding secrets in your application; use environment variables or secure storage.
  2. Set Expiration Times

    • Set appropriate expiration times for JWTs (exp claim) to limit their validity. Use refresh tokens to renew access when needed without compromising security.
  3. Use HTTPS

    • Always transmit JWTs over secure channels (HTTPS) to prevent interception and replay attacks.
  4. Avoid Storing Sensitive Data in JWTs

    • Do not store sensitive information, such as passwords or credit card details, in JWTs, as they are easily decoded. Use JWTs only for claims that are safe to expose.
  5. Implement Token Revocation

    • Implement mechanisms to revoke tokens, especially in the case of compromised credentials. This can be done by maintaining a blacklist or adjusting token validity dynamically.
  6. Validate and Sanitize JWT Data

    • Validate all data received within the JWT and ensure that user input, even within tokens, is properly sanitized to prevent injection attacks.
  7. Monitor and Log Authentication Events

    • Log authentication events, including successful and failed login attempts, to monitor for suspicious activity and potential security breaches.

7. Conclusion

JWTs provide a powerful and flexible way to handle authentication and authorization in REST APIs. By leveraging JWTs, developers can create secure, scalable, and stateless authentication systems that simplify session management while enhancing security. Following best practices for using JWTs, such as setting expiration times, using secure transmission, and avoiding sensitive data storage, ensures that APIs remain robust and protected against unauthorized access.