Securing Your REST API with Quarkus: Implementing an API Key Request Filter

Andreas Eberle
arconsis
Published in
4 min readAug 17, 2023
Imagined by Midjourney

In the world of modern web applications, security is paramount. Ensuring that only authorized users and systems can access your REST API is a crucial step in safeguarding your data and resources. In this article, we’ll explore how to implement a robust security mechanism using Quarkus with Kotlin.

Imagine a scenario where you’re developing a REST API that provides sensitive data or performs critical operations. You want to ensure that only authorized clients can interact with your API, preventing unauthorized access. One of the easiest and most effective ways to achieve this is by implementing an API key request filter.

In the following, we’ll walk through the process of creating a custom API key request filter using Quarkus. This filter will intercept incoming API requests, validate the provided API key, and grant access only to requests with valid keys. By the end of this guide, you’ll have a solid understanding of how to enhance the security of your Quarkus-powered REST API.

Prefer learning through videos? We’ve got you covered. Alongside the detailed written guide, we’ve also prepared a video tutorial where I explain each step of the process. Whether you’re a visual learner or you prefer a hands-on demonstration, this video will complement the written content perfectly.

Example Code: To help you follow along and dive deeper into the implementation details, I’ve prepared a GitHub repository with the complete source code for the API key filter with Quarkus & Kotlin. You can find the code at https://github.com/arconsis/quarkus-kotlin-api-key-filter.

Implementing the API Key Filter

To secure your REST API with an API key filter in Quarkus, follow these simple steps:

Step 1: Create the Filter Function

Start by creating a new class to house your API key filter logic. This separation of concerns promotes clean and organized code. Let’s call this class MyApiKeyFilter.

class MyApiKeyFilter {

@ServerRequestFilter(preMatching = true)
fun filterApiKey(containerRequestContext: ContainerRequestContext){
// Your API key validation logic comes here
}
}

In the MyApiKeyFilter class, we add a function filterApiKey (the names can be chosen freely). This function must be annotated with @ServerRequestFilter so that Quarkus calls it when it evaluates the request filter chain. Furthermore, we also set preMatching=true to ensure that Quarkus runs the filter early in the request evaluation process, allowing you to filter out requests without a valid API key as soon as possible.

To be able to access the request from within the filter function, we add a parameter of type ContainerRequestContext, which will be injected by Quarkus.

Step 2: Implement the Filter Logic

To validate the incoming request, we get the x-api-key header from the ContainerRequestContext and compare it with our own API key. If the header’s value isn’t equal to our API key, we abort the request by throwing and UnauthorizedException.

class MyApiKeyFilter {

@ServerRequestFilter(preMatching = true)
fun filterApiKey(containerRequestContext: ContainerRequestContext) {
val apiKeyHeader = containerRequestContext.getHeaderString("x-api-key")

if (apiKeyHeader != "my-awesome-api-key") {
throw UnauthorizedException()
}
}
}

Step 3: Inject the API Key(s)

Of course, you don’t want to hard-code the API key but e.g. rather inject it depending on the environment. Additionally, an API often has multiple clients and we want them to use different API keys so we can identify their calls.

We achieve this by adding a constructor to the class MyApiKeyFilter and inject a list of String from the configuration property of your choice.

import io.quarkus.security.UnauthorizedException
import jakarta.ws.rs.container.ContainerRequestContext
import org.eclipse.microprofile.config.inject.ConfigProperty
import org.jboss.resteasy.reactive.server.ServerRequestFilter

class MyApiKeyFilter(@ConfigProperty(name = "my.application.api-keys") private val apiKeys: List<String>) {

@ServerRequestFilter(preMatching = true)
fun filterApiKey(containerRequestContext: ContainerRequestContext) {
val apiKeyHeader = containerRequestContext.getHeaderString("x-api-key")

if (!apiKeys.contains(apiKeyHeader)) {
throw UnauthorizedException()
}
}
}

The configuration property my.application.api-keys can be defined in the application.properties file and contains a comma-separated list of allowed API keys.

my.application.api-keys=my-api-key-1,my-api-key-2

To modify the available API keys at runtime, you can either provide them via an environment variable of the same name (i.e. MY_APPLICATION_API_KEYS) or by using another environment variable in the application.properties to assign its value to the my.application.api-keys property.

Step 4: Test Your API Key Filter

Now you can test your API Key filter by sending requests to your protected API endpoints. Requests without a valid API Key should be blocked and receive an appropriate error response.

With your newly implemented API Key filter, you’ve significantly enhanced the security of your Quarkus-powered REST API. Unauthorized access attempts will be intercepted and denied at an early stage, ensuring that only valid clients can access your valuable resources.

Now, go ahead and strengthen your API’s defenses with the power of Quarkus and API Key authentication!

--

--

Andreas Eberle
arconsis

Solutions Architect & Software Engineer @arconsis