Introduction

Welcome to the lesson on Token-Based Authentication with Cookies and JWT Expiration. In our previous lesson, we explored account lockout and enumeration prevention, which are crucial for securing web applications. Today, we'll dive deeper into token-based authentication, a key component of modern application security. We'll focus on advanced features like the use of cookies and token expiration. These concepts are essential for maintaining secure and efficient authentication processes in your applications. Let's get started! 🚀

Securing JSON Web Tokens (JWT)

In the previous unit, we focused on only one authentication method: using user credentials, specifically username and password. JSON Web Tokens (JWT) are commonly used for secure authentication. However, as more resources gain access to an account, the number of potential attack vectors increases. What happens if JWTs are exposed? Here, we present strategies to enhance the security of JWTs.

Limiting Damage from Token Theft

In previous courses, we mainly focused on how attackers can exploit vulnerabilities. However, ensuring security in applications not only includes mitigating the chances of an outage but also focuses on reducing the harm when a breach has already occurred. Imagine that an attacker somehow stole your JWT token (one example is that an engineer posted unnecessary debug results, including this data, on an online Q&A platform). One way to limit the attacker's actions is to implement a token expiration mechanism.

Implementing Token Expiration

Previously, you may have seen code that creates a token without an expiration time, which is vulnerable. Token expiration is a critical feature that ensures tokens are valid only for a limited time, reducing the risk of misuse.

Here is how you can generate a JWT with an expiration time in Java using the jjwt library:

In this example, the token will expire in one hour. This limits the window of opportunity for an attacker to use a stolen token.

The Problem with Returning JWT Tokens in the Response Body

Returning JWT tokens in the response body (for example, using ResponseEntity in Spring or writing directly to the response in a servlet) can expose tokens to potential security risks. When tokens are included in JSON responses, they can be accessed by JavaScript running in the browser, making them vulnerable to cross-site scripting (XSS) attacks. If an attacker injects malicious scripts into your application, they could potentially access these tokens and use them to impersonate users.

What are Cookies and How Can They Be Used?

Cookies are small pieces of data stored on the client side, typically used to maintain session state between the client and server. They offer a more secure way to handle tokens when configured with specific attributes:

  • HTTP-Only: This attribute prevents JavaScript from accessing the cookie, reducing the risk of XSS attacks.
  • Secure: Ensures that cookies are only sent over HTTPS, protecting them from being intercepted over insecure connections.
  • SameSite: Controls how cookies are sent with cross-site requests, helping to mitigate cross-site request forgery (CSRF) attacks.

By using cookies to store JWT tokens, you can enhance the security of your authentication process.

Implementing Token Handling with Cookies

Here's how you can implement token handling using cookies in a Spring Boot application. The following example uses a Spring Boot controller to demonstrate setting a JWT in an HTTP-only cookie after a successful login:

Explanation:
  • Setting Cookies: The Cookie object is used to store the access token in a cookie with security attributes (HttpOnly, Secure). The SameSite attribute is set manually in the header using response.setHeader().
  • Short-lived Tokens: The token is set to expire in 15 minutes, minimizing the risk of misuse if intercepted.
  • Spring Boot Controller: This example uses @RestController and @PostMapping annotations, which is the standard approach in Spring Boot applications.

By leveraging cookies for token storage, you can significantly improve the security of your authentication mechanism while maintaining a seamless user experience.

Handling Cookies in the Frontend

When using cookies to store JWT tokens, the browser manages the cookies automatically. This means that, for most requests to your Java web application, the browser will include the relevant cookies without any additional code on the client side. The frontend does not need to directly access or manage the token.

Token Revocation Challenges and Solutions

While we've implemented token expiration and secure cookie handling, there's an important limitation to address: once a JWT is issued, it remains valid until expiration. This creates security challenges in several scenarios:

  1. User Logout: When a user explicitly logs out, their token should be immediately invalidated, not wait for expiration.
  2. Compromised Tokens: If a token is stolen or compromised, you need to revoke it immediately to prevent unauthorized access.
  3. Password Changes: When a user changes their password, all existing tokens should be invalidated.
  4. Account Suspension: Administrative actions like suspending a user account should immediately revoke all their tokens.

JWTs are designed to be stateless - the server doesn't need to store them or look them up in a database. This is both their strength (scalability) and their weakness (no revocation mechanism). Once signed, a JWT is valid for its entire lifetime unless you implement additional mechanisms.

Approaches to Token Revocation

There are several strategies to handle token revocation, each with trade-offs:

  1. Token Blacklisting: Maintain a list of revoked tokens (in Redis or a database) and check against it on each request:

Trade-off: Reintroduces state, reducing the stateless benefit of JWTs.

  1. Short-Lived Access Tokens + Refresh Tokens: Use very short-lived access tokens (5-15 minutes) paired with longer-lived refresh tokens. This limits the window of vulnerability:

When the access token expires, the client uses a refresh token to get a new one. The refresh token can be revoked server-side. We'll explore this pattern in detail in the next unit.

  1. Token Versioning: Include a version number in the token and store the current valid version server-side:

When you need to revoke all tokens, simply increment the user's token version.

Practical Considerations

For most applications, we recommend:

  • Short-lived access tokens (15 minutes or less)
  • Refresh token mechanism for maintaining sessions (covered in Unit 3)
  • Token blacklisting only for critical revocations (account compromise, admin actions)

The shorter the access token lifetime, the less critical immediate revocation becomes, as compromised tokens expire quickly. In the next unit, we'll implement a complete refresh token system that balances security, user experience, and scalability.

Including Cookies in Requests

Browsers automatically include cookies with requests to the same origin. If your Java web application is accessed from a different origin (for example, if your frontend and backend are on different domains or ports), you need to ensure that your server is configured to allow credentials in cross-origin requests. This is typically done by setting the appropriate CORS headers in your Java application, such as:

This allows the browser to send cookies along with requests to your backend.

Handling Authentication State

In Java web applications, authentication state can be managed in several ways. When using token-based authentication with cookies, the server can validate the JWT token included in the cookie for each request. If the token is valid and not expired, the user is considered authenticated. You can also use session attributes or a security context to track the user's authentication state within the application.

For example, after validating the token, you might set a user attribute in the request or session to indicate that the user is logged in. If the token is missing or invalid, you can redirect the user to the login page or return an appropriate error response.

Real-World Applications and Security Challenges

Advanced token-based authentication is crucial in real-world scenarios, such as microservices architectures and mobile applications. These systems often require secure, scalable, and efficient authentication mechanisms. However, they also present unique security challenges, such as managing token lifecycles and preventing unauthorized access.

By understanding and implementing advanced token-based authentication features, you can build more secure applications. Always consider potential security challenges and address them proactively to protect your users and data.

Conclusion and Next Steps

In this lesson, we explored advanced token-based authentication, focusing on token expiration and the use of cookies for secure token storage. These features are essential for maintaining secure and efficient authentication processes in your applications. As you move on to the practice exercises, apply what you've learned to reinforce your understanding. Consider exploring further topics like OAuth2 or OpenID Connect to continue your learning journey. Keep up the great work! 🌟

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