Spring Security: Implementing Unique CSRF Token for Every Request

Alexander Obregon
6 min readAug 4, 2023
Image Source

Introduction

One of the critical aspects of web application security is CSRF (Cross-Site Request Forgery) protection. CSRF attacks trick the victim into submitting a malicious request, leveraging their identity and privileges. The Spring Security framework provides strong CSRF protection, which developers can use to safeguard their applications.

In most cases, the CSRF token remains the same for an entire session for performance reasons. But what if your application requires a unique CSRF token for every request? Let’s delve into how you can implement this in Spring MVC.

Understanding the CSRF Token Life Cycle

Before jumping into the solution, let’s quickly review the life cycle of a CSRF token in Spring Security. When a request is made, Spring Security checks for the CSRF token in the request. If not found, it generates a new one. Once the CSRF token is used, it remains the same for the whole session.

Creating a Unique CSRF Token for Every Request

To create a unique CSRF token for every request, we need to override the default behavior of Spring Security. We’ll do this by implementing a custom CsrfTokenRepository. Our custom repository will always generate a new CSRF token for each request and won't store any tokens between requests.

Here’s what our custom CsrfTokenRepository would look like:

import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PerRequestCsrfTokenRepository implements CsrfTokenRepository {
private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

@Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(DEFAULT_CSRF_HEADER_NAME, DEFAULT_CSRF_PARAMETER_NAME, createNewToken());
}

@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
// No need to save the token since a new one will be created for every request
}

@Override
public CsrfToken loadToken(HttpServletRequest request) {
// Always return null so that a new token is created for every request
return null;
}

private String createNewToken() {
// Your logic for creating a new CSRF token here
return new String(Base64.getUrlEncoder().withoutPadding().encode(UUID.randomUUID().toString().getBytes()));
}
}

This PerRequestCsrfTokenRepository creates a new CSRF token for every request by overriding the loadToken() method to always return null. The saveToken() method does nothing because we don't need to store the token.

Stateless CSRF Token Verification

In the context of enhancing CSRF protection by generating a unique token for each request, a common question arises: how can these tokens be verified if they are not stored? This section goes into the stateless verification process, which ensures strong security without the need to save each token on the server.

The Principle of Stateless Verification

Stateless CSRF protection operates on the premise that the server can validate a token without retaining a copy of it. This method leverages cryptographic techniques to create tokens that carry inherent validation criteria. By doing so, the server can ascertain the legitimacy of a token based solely on its content and structure, without the need for comparison against a stored version.

Generating Verifiable Tokens

The key to stateless verification lies in the secure generation of tokens. Each token is crafted using a combination of unique identifiers and cryptographic signatures. For instance, a token might include a user-specific identifier, a timestamp to ensure time-bound validity, and a cryptographic hash that binds these elements together. The hash function would use a secret key, known only to the server, ensuring that only tokens generated by the server can be considered valid. Here is an example of how a token might be generated:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.UUID;

public String generateToken(String userId) {
try {
String data = userId + ":" + System.currentTimeMillis();
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec("YourSecretKey".getBytes(), "HmacSHA256");
mac.init(secret_key);
String signature = Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
return Base64.getEncoder().encodeToString((data + "." + signature).getBytes());
} catch (Exception e) {
throw new RuntimeException("Error generating token", e);
}
}

This code snippet demonstrates generating a CSRF token using a combination of the user’s ID, a timestamp, and a cryptographic signature. The HmacSHA256 algorithm ensures that the token is securely signed with a secret key.

Verifying Tokens Without Storage

Upon receiving a request with an attached CSRF token, the server deciphers the token to extract its components. It then reconstructs the cryptographic hash using the extracted information and the secret key. If the newly computed hash matches the one embedded in the token, the token is deemed valid. This process confirms that the token was generated by the server and has not been tampered with.

This verification method also allows for the implementation of additional security checks, such as:

  • Timestamp Validation: By examining the timestamp within the token, the server can determine whether the token is still within its valid time frame, reducing the risk of replay attacks.
  • One-Time Use Enforcement: The server can implement mechanisms to ensure each token is used only once, further enhancing security. This could involve lightweight tracking methods or leveraging the inherent properties of the token generation process to naturally enforce one-time use.

Here’s how the verification process might be implemented:

public boolean verifyToken(String token, String userId) {
try {
byte[] decodedBytes = Base64.getDecoder().decode(token);
String[] parts = new String(decodedBytes).split("\\.");
if (parts.length != 3) return false;

String data = parts[0] + "." + parts[1];
String signature = parts[2];

Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec("YourSecretKey".getBytes(), "HmacSHA256");
mac.init(secret_key);
String computedSignature = Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));

return signature.equals(computedSignature) && data.startsWith(userId + ":");
} catch (Exception e) {
return false;
}
}

This example demonstrates how a server could verify a CSRF token. It decodes the token, separates the data and signature, and then computes the signature again using the same secret key. If the computed signature matches the token’s signature and the data part contains the correct user ID, the token is considered valid.

Advantages of Stateless Verification

Stateless CSRF token verification offers several advantages, particularly in distributed or high-traffic environments:

  • Scalability: Eliminating the need to store tokens server-side reduces the resource burden, facilitating scalability.
  • Performance: Stateless verification can be more performant at scale, as it avoids the overhead associated with token storage and retrieval.
  • Flexibility: This approach is well-suited to modern, distributed web applications where session management might be decentralized or handled by external services.

Considerations for Implementation

While stateless CSRF protection provides a compelling approach to securing applications, it requires careful implementation to ensure the security of the token generation and verification process. Developers must ensure the use of strong cryptographic methods and manage the secret key securely to prevent compromise.

Using PerRequestCsrfTokenRepository in Your Application

To use PerRequestCsrfTokenRepository, modify your Spring Security configuration as follows:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(new PerRequestCsrfTokenRepository())
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}

Performance Implications

While this solution can be handy in specific use cases, bear in mind the performance implications. Generating a new CSRF token for every request can be computationally expensive, and not saving tokens means you cannot reuse them. Thus, it may not be suitable for all applications, particularly those with high traffic.

Conclusion

This article explored how to generate a unique CSRF token for each request in Spring Security. This is just a couple examples of how adaptable Spring Security can be, tailored to meet specific security requirements. However, always be aware of potential performance trade-offs when implementing such custom security configurations.

For more information on CSRF protection in Spring Security, refer to the Spring Security Reference Documentation.

I hope you find this guide helpful. As always, if you have any questions or comments, feel free to leave them below!

  1. OWASP CSRF Prevention Cheat Sheet
  2. Baeldung’s Spring Security Tutorials

Special thanks to Ankushmali for prompting this discussion.

Spring Boot icon by Icons8

--

--

Alexander Obregon

Software Engineer, fervent coder & writer. Devoted to learning & assisting others. Connect on LinkedIn: https://www.linkedin.com/in/alexander-obregon-97849b229/