Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 26

Extracting API Keys from HTTP Headers

Recommended resources

Hello! Welcome back to our project.

In the previous lesson, we established how to access fundamental request data like IP addresses and headers using the HttpServletRequest object within our RateLimitingInterceptor. You learned about the importance of the X-Forwarded-For header and confirmed that the interceptor is the right place to centralize our identification logic.

Now, we'll put that knowledge into practice. This lesson moves from the general concept of data extraction to the specific implementation of one of our core multi-tenant strategies.

Your learning outcome for this lesson:

You will implement a key resolver to extract an API key from HTTP headers. By the end of this lesson, you will have created a concrete ApiKeyResolver class that conforms to our RateLimitKeyResolver interface, making our rate limiter capable of identifying clients based on a custom header.



1. The Role of the API Key Resolver

In Module 2, we designed a RateLimitKeyResolver interface to abstract the logic of generating a unique key for each request. This follows the Strategy design pattern, allowing us to easily switch between different identification methods (IP, API key, User ID, etc.).

Today, we'll implement the strategy for API keys. An API key is a common mechanism for identifying and authenticating application clients, often tied to specific subscription tiers or access levels.

Our resolver will have a single, focused responsibility: inspect the incoming request for a specific HTTP header (e.g., X-Api-Key) and return its value as the rate limit key.


2. Implementing the ApiKeyResolver

Let's start by recalling the interface we designed. It's a simple, functional interface whose job is to produce a String key from a request.

// Assumed from our design in Module 2
public interface RateLimitKeyResolver {
    /**
     * Resolves a key from the incoming HTTP request.
     * @param request the current HTTP request
     * @return A string key for rate limiting, or null/empty if a key cannot be resolved.
     */
    String resolve(HttpServletRequest request);
}

Now, let's create our concrete implementation. We'll make it a Spring @Component so it can be easily injected into our RateLimitingInterceptor. We will also define the header name as a constant for clarity and maintainability.

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Component("apiKeyResolver") // Naming the bean for clarity
public class ApiKeyResolver implements RateLimitKeyResolver {

    private static final String API_KEY_HEADER = "X-Api-Key";

    @Override
    public String resolve(HttpServletRequest request) {
        String apiKey = request.getHeader(API_KEY_HEADER);

        // Return the key only if it's present and not empty.
        // The calling logic will handle cases where the key is absent.
        if (StringUtils.hasText(apiKey)) {
            return apiKey;
        }

        return null;
    }
}

Key points about this implementation:

  • Dependency: It only depends on the jakarta.servlet.http.HttpServletRequest, keeping it decoupled from the rest of the Spring framework.
  • Simplicity: The logic is straightforward, using the getHeader() method we explored in the last lesson.
  • Robustness: We use StringUtils.hasText() to check that the header is not only present but also contains non-whitespace characters.
  • Return Value: It returns null if the header is missing or empty. This is a clean way to signal that this particular resolver could not find a key, allowing our system to potentially try another resolver or apply a default policy.

3. Integrating the Resolver into the Interceptor

With our ApiKeyResolver ready, let's see how it fits into the RateLimitingInterceptor. The interceptor will be configured with a specific RateLimitKeyResolver implementation and will delegate the key generation task to it.

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class RateLimitingInterceptor implements HandlerInterceptor {

    private final RateLimiter rateLimiter;
    private final RateLimitKeyResolver keyResolver;

    // Injecting our specific API key resolver
    public RateLimitingInterceptor(RateLimiter rateLimiter, 
                                   @Qualifier("apiKeyResolver") RateLimitKeyResolver keyResolver) {
        this.rateLimiter = rateLimiter;
        this.keyResolver = keyResolver;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String key = keyResolver.resolve(request);

        if (key == null) {
            // Policy decision: what to do if no key is found?
            // For now, let's assume we allow requests without keys to pass.
            // In a real system, you might deny them.
            return true; 
        }

        if (rateLimiter.isAllowed(key)) {
            return true; // Proceed with the request
        }

        // Limit exceeded
        response.setStatus(429); // HTTP 429 Too Many Requests
        response.getWriter().write("Rate limit exceeded.");
        return false; // Block the request
    }
}

Notice the use of @Qualifier("apiKeyResolver"). This tells Spring to inject the specific bean we named earlier. This pattern will be essential as we create more resolver implementations.


4. Real-World Context and Patterns

Extracting a header is simple, but it's the foundation for powerful, business-driven logic. The following resources show how this pattern is applied in production-grade systems.

The article below from Baeldung discusses implementing rate limiting with the Bucket4j library. It perfectly demonstrates how an API key, extracted from a header, is used to select a specific rate-limiting plan.

Rate Limiting a Spring API Using Bucket4j

This article provides an excellent, end-to-end example of API key-based rate limiting. Pay close attention to how they move from a controller-based approach to a more scalable HandlerInterceptor implementation, just like we are.

Please read sections 5.3 'API Clients and Pricing Plan' and 5.4 'Using Spring MVC Interceptor'. In 5.3, notice how resolvePlanFromApiKey uses the key to enforce different limits. This is what our rate limiter engine will eventually do. In 5.4, observe their RateLimitInterceptor. It extracts the X-api-key header in preHandle, resolves a bucket (our equivalent is calling isAllowed), and returns false to block the request on failure. This pattern directly mirrors our own implementation.

For another perspective, consider how API gateways use this same principle for security. The video below shows how Spring Cloud Gateway uses an API key from a header to authenticate and route requests. While the goal is authentication, the core mechanism of extracting and validating a key from a header is identical.

Securing Microservices with API Key Based Auth - Spring Cloud Gateway | JavaTechie

This video from Java Techie demonstrates API key authentication in a Spring Cloud Gateway. This is a great example of an "infra problem" solution where the same key extraction pattern is used for a different purpose (authentication vs. rate limiting).

Watch the segment from 29:36 to 33:10. The presenter builds an AuthFilter (which serves a similar role to our HandlerInterceptor). Inside the filter method, you'll see the code to get the 'gateway-key' from the request headers. This reinforces the universal nature of the pattern you've just implemented.

These examples should give you confidence that our ApiKeyResolver is a standard, robust, and essential component in building API infrastructure.


Key Takeaways

  • You have successfully implemented a concrete ApiKeyResolver that adheres to the RateLimitKeyResolver strategy interface.
  • This resolver extracts a specific header (X-Api-Key) from the HttpServletRequest and can be injected directly into our RateLimitingInterceptor.
  • Using a constant for the header name (API_KEY_HEADER) improves code clarity and maintainability.
  • Returning null when a key is not found is a clean signal that allows the calling code to implement fallback or denial logic.
  • This pattern of extracting a key from a header is a fundamental building block for both rate limiting and API security.

Next Steps

Excellent work! We now have a functioning key resolver for one of the most common identification methods. In the next lesson, we will continue expanding our multi-tenant capabilities by implementing a resolver that identifies a client by their User ID, typically extracted from a security context after they have logged in. This will introduce another important dimension to our rate limiting engine.