Cross-Site Request Forgery(CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated.

Quarkus Security provides a CSRF prevention feature which consists of a Resteasy Reactive server filter which creates and verifies CSRF tokens and an HTML form parameter provider which supports the injection of CSRF tokens in Qute templates.

Creating the Project

First, we need a new project. Create a new project with the following command:

CLI
quarkus create app org.acme:security-csrf-prevention \
    --extension=csrf-reactive \
    --no-code
cd security-csrf-prevention

To create a Gradle project, add the --gradle or --gradle-kotlin-dsl option.

For more information about how to install the Quarkus CLI and use it, please refer to the Quarkus CLI guide.

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.13.0.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-csrf-prevention \
    -Dextensions="csrf-reactive" \
    -DnoCode
cd security-csrf-prevention

To create a Gradle project, add the -DbuildTool=gradle or -DbuildTool=gradle-kotlin-dsl option.

This command generates a project which imports the csrf-reactive extension.

If you already have your Quarkus project configured, you can add the csrf-reactive extension to your project by running the following command in your project base directory:

CLI
quarkus extension add 'csrf-reactive'
Maven
./mvnw quarkus:add-extension -Dextensions="csrf-reactive"
Gradle
./gradlew addExtension --extensions="csrf-reactive"

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-csrf-reactive</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-csrf-reactive")

Next lets add a Qute template producing an HTML form:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>User Name Input</title>
</head>
<body>
    <h1>User Name Input</h1>

    <form action="/service/csrfTokenForm" method="post">
            <input type="hidden" name="{inject:csrf.parameterName}" value="{inject:csrf.token}" />  (1)

            <p>Your Name: <input type="text" name="name" /></p>
            <p><input type="submit" name="submit"/></p>
    </form>
</body>
</html>
1 This expression is used to inject a CSRF token into a hidden form field. This token will be verified by the CSRF filter against a CSRF cookie.

You can name the file containing this template as csrfToken.html and put it in a src/main/resources/templates folder.

Now let’s create a resource class which returns an HTML form and handles form POST requests:

package io.quarkus.it.csrf;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;

@Path("/service")
public class UserNameResource {

    @Inject
    Template csrfToken; (1)

    @GET
    @Path("/csrfTokenForm")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance getCsrfTokenForm() {
        return csrfToken.instance(); (2)
    }

    @POST
    @Path("/csrfTokenForm")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    public String postCsrfTokenForm(@FormParam("name") String name) {
        return userName; (3)
    }
}
1 Inject the csrfToken.html as a Template.
2 Return HTML form with a hidden form field containing a CSRF token created by the CSRF filter.
3 Handle the form POST request, this method can only be invoked only if the CSRF filter has successfully verified the token.

The form POST request will fail with HTTP status 400 if the filter finds the hidden CSRF form field is missing, the CSRF cookie is missing, or if the CSRF form field and CSRF cookie values do not match.

At this stage no additional configuration is needed - by default the CSRF form field and cookie name will be set to csrf_token, and the filter will verify the token. But lets change these names:

quarkus.csrf-reactive.form-field-name=csrftoken
quarkus.csrf-reactive.cookie-name=csrftoken

Note that the CSRF filter has to read the input stream in order to verify the token and then re-create the stream for the application code to read it as well. The filter performs this work on an event loop thread so for small form payloads such as the one shown in the example above it will have negligible peformance side-effects. However if you deal with large form payloads then it is recommended to compare the CSRF form field and cookie values in the application code:

package io.quarkus.it.csrf;

import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;

@Path("/service")
public class UserNameResource {

    @Inject
    Template csrfToken;

    @GET
    @Path("/csrfTokenForm")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance getCsrfTokenForm() {
        return csrfToken.instance();
    }

    @POST
    @Path("/csrfTokenForm")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    public String postCsrfTokenForm(@CookieParam("csrf-token") csrfCookie, @FormParam("csrf-token") String formCsrfToken, @FormParam("name") String userName) {
        if (!csrfCookie.getValue().equals(formCsrfToken)) { (1)
            throw new BadRequestException();
        }
        return userName;
    }
}
1 Compare the CSRF form field and cookie values and fail with HTTP status 400 if they don’t match.

Also disable the token verification in the filter:

quarkus.csrf-reactive.verify-token=false

Configuration Reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

If filter is enabled.

Environment variable: QUARKUS_CSRF_REACTIVE_ENABLED

boolean

true

Form field name which keeps a CSRF token.

Environment variable: QUARKUS_CSRF_REACTIVE_FORM_FIELD_NAME

string

csrf-token

CSRF cookie name.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_NAME

string

csrf-token

CSRF cookie max age.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_MAX_AGE

Duration

10M

CSRF cookie path.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_PATH

string

/

CSRF cookie domain.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_DOMAIN

string

If enabled the CSRF cookie will have its 'secure' parameter set to 'true' when HTTP is used. It may be necessary when running behind an SSL terminating reverse proxy. The cookie will always be secure if HTTPS is used even if this property is set to false.

Environment variable: QUARKUS_CSRF_REACTIVE_COOKIE_FORCE_SECURE

boolean

false

Create CSRF token only if the HTTP GET relative request path is the same as the one configured with this property.

Environment variable: QUARKUS_CSRF_REACTIVE_CREATE_TOKEN_PATH

string

The random CSRF token size in bytes.

Environment variable: QUARKUS_CSRF_REACTIVE_TOKEN_SIZE

int

16

Verify CSRF token in the CSRF filter. If this property is enabled then the input stream will be read by the CSRF filter to verify the token and recreated for the application code to read the data correctly. Therefore, it is recommended to disable this property when dealing with the large form payloads and instead compare CSRF form and cookie parameters in the application code using JAX-RS FormParam which refers to the form-field-name form property and CookieParam which refers to the CsrfReactiveConfig#cookieName cookie. Note that even if the CSRF token verification in the CSRF filter is disabled, the filter will still perform checks to ensure the token is available, has the correct token-size in bytes and that the Content-Type HTTP header is 'application/x-www-form-urlencoded'.

Environment variable: QUARKUS_CSRF_REACTIVE_VERIFY_TOKEN

boolean

true

Require that only 'application/x-www-form-urlencoded' body is accepted for the token verification to proceed. Disable this property for the CSRF filter to avoid verifying the token for POST requests with other content types. This property is only effective if verify-token property is enabled.

Environment variable: QUARKUS_CSRF_REACTIVE_REQUIRE_FORM_URL_ENCODED

boolean

true

About the Duration format

The format for durations uses the standard java.time.Duration format. You can learn more about it in the Duration#parse() javadoc.

You can also provide duration values starting with a number. In this case, if the value consists only of a number, the converter treats the value as seconds. Otherwise, PT is implicitly prepended to the value to obtain a standard java.time.Duration format.

References