Introduction to JWT Security

In today's digital landscape, securing user authentication is paramount. JSON Web Tokens (JWTs) have emerged as a popular solution for implementing secure, stateless authentication in web applications. This course, "JWT Security: Attacks & Defenses," will guide you through the intricacies of JWTs, helping you understand their benefits, potential vulnerabilities, and how to safeguard your applications against attacks. You may have encountered JWTs in previous courses within this learning path, and now we'll delve deeper into their security aspects.

Understanding JWT Basics

JSON Web Tokens (JWTs) have become a standard method for implementing authentication in modern web applications. A JWT is a compact, self-contained way to securely transmit information between parties as a JSON object. This information can be verified and trusted because it is digitally signed by the server.

JWTs consist of three parts, separated by dots (.):

  1. Header - Contains the type of token and the signing algorithm being used.
  2. Payload - Contains the claims or the data being transmitted. The payload is essentially a dictionary (key-value pairs) that holds all the session data you want to include, such as the user ID, roles, or any other relevant information. This data is not encrypted, but it is protected by the signature.
  3. Signature - Ensures the token hasn't been altered. The signature is created by the server using a secret key known only to the server.

Here's what a JWT looks like:

The main benefit of using JWTs for authentication is that they are stateless. Unlike traditional session-based authentication, where session information is stored on the server, JWTs contain all the necessary information within themselves (in the payload). This makes them particularly useful for:

  • Single Sign-On (SSO) scenarios
  • API authentication
  • Microservices architectures
  • Mobile applications

With JWTs, your server doesn't need to query a database to validate a user's session on every request, which can improve performance and scalability.

Decoding a JWT

Although the JWT is signed, its header and payload are simply Base64Url-encoded and can be decoded by anyone. This means you can inspect the contents of a JWT without needing the secret key—only the signature verification requires the secret. In Node.js, you can decode the header and payload like this:

This allows you to see what data is stored in the token, but remember: decoding does not verify the token’s authenticity or integrity.

How JWT Security Works

A JWT is secure because it is signed by the server using a secret key (JWT_SECRET_KEY) that is stored only on the server. Here’s how the process works:

  • When a user logs in, the server generates a JWT token containing the session data (payload) and signs it with the secret key.
  • The server sends this token to the client (for example, in a cookie or HTTP header).
  • On subsequent requests, the client sends the JWT token back to the server.
  • The server uses the secret key to verify the token’s signature and parse the payload. If the token is valid and unaltered, the server trusts the session data inside.

Important: The secret key is never shared with the client or anyone outside the server. Only the server can generate or verify JWT tokens. The client cannot construct or modify a valid JWT token, nor can it parse or verify the signature without the secret. The diagram above illustrates the typical flow of JWT (JSON Web Token) authentication between a user, a client application, and an API server. Here's a step-by-step breakdown:

  1. Login Request The user provides their login credentials (usually username and password) through the Client App, which then sends a login request to the API.

  2. JWT Generation The API validates the credentials. If valid, it generates a JWT using a secret key. This token encodes user identity and access claims.

  3. Token Sent to Client The JWT is returned to the Client App, which stores it—typically in memory, local storage, or a cookie—for future use.

  4. Authenticated Requests For subsequent interactions, the Client App includes the JWT in the Authorization header (usually as Bearer <token>) when making requests to the API. The API verifies the token using the same secret key and, if valid, processes the request accordingly.

Setting Up JWT Authentication in TypeScript

Let's create an authentication router that handles user login and token generation. (Note: Registration is not directly related to JWT, so we’ll focus on the JWT-related endpoints.)

In this code, we're importing the necessary modules including cookie-parser and setting up an Express router. The generateJWTToken function creates a JWT containing the user's ID as the payload. The token is signed with a secret key and set to expire in one hour.

The JWT_SECRET_KEY should be a strong, random string stored securely in your application's configuration. Never hardcode this value directly in your source code or commit it to version control.

Secure JWT Storage and Verification

Now, let's implement the login endpoint that generates and stores the JWT securely:

This login endpoint:

  1. Verifies the user's credentials.
  2. Generates a JWT if authentication is successful.
  3. Stores the token in an HTTP-only cookie.

Note: Cookies are not required for JWT authentication. They are just one way to transfer and persist the JWT token between the client and server. You could also return the token in the HTTP response body and have the client store it in memory or in localStorage. However, using HTTP-only cookies is generally more secure because they cannot be accessed by JavaScript, which helps protect against Cross-Site Scripting (XSS) attacks. The secure flag ensures the cookie is only sent over HTTPS connections, and the sameSite: 'strict' setting helps prevent Cross-Site Request Forgery (CSRF) attacks.

JWT Verification Middleware

Next, let's implement the JWT verification middleware that will protect our routes:

This middleware:

  1. Extracts the token from the request cookies.
  2. Verifies the token's signature and expiration using the server's secret key.
  3. Attaches the user ID to the request object if verification succeeds.
  4. Calls the next middleware in the chain.

If the token is missing, invalid, or expired, the middleware returns an appropriate error response.

Summary

In this lesson, we explored the fundamentals of JWT-based authentication, including its structure, benefits, and secure implementation in TypeScript. We clarified that the JWT payload contains all session data as a dictionary, and that JWT security relies on a server-only secret key for signing and verifying tokens. The server generates and verifies JWT tokens, while the client simply stores and returns them. We also discussed that cookies are just one way to transfer JWTs, not a required part of JWT itself. Middleware plays a key role in verifying JWTs and securing routes. Let’s practice our knowledge with the upcoming exercises!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal