# Cross-Site Request Forgery Prevention

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 implements Double Submit Cookie and CSRF Request Header techniques.

`Double Submit Cookie` technique requires that the CSRF token sent as `HTTPOnly`, optionally signed, cookie to the client, and directly embedded in a hidden form input of server-side rendered HTML forms, or submitted as a request header value.

The extension consists of a Quarkus REST (formerly RESTEasy Reactive) server filter which creates and verifies CSRF tokens in `application/x-www-form-urlencoded` and `multipart/form-data` forms and a Qute 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='rest-csrf' \
        --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 and use the Quarkus CLI, see the Quarkus CLI guide.

Maven

    mvn io.quarkus.platform:quarkus-maven-plugin:3.20.2.1:create \
        -DprojectGroupId=org.acme \
        -DprojectArtifactId=security-csrf-prevention \
        -Dextensions='rest-csrf' \
        -DnoCode
    cd security-csrf-prevention

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

For Windows users:

- If using cmd, (don’t use backward slash `\` and put everything on the same line)

- If using Powershell, wrap `-D` parameters in double quotes e.g. `"-DprojectArtifactId=security-csrf-prevention"`

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

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

CLI

    quarkus extension add rest-csrf

Maven

    ./mvnw quarkus:add-extension -Dextensions='rest-csrf'

Gradle

    ./gradlew addExtension --extensions='rest-csrf'

This will add the following to your build file:

pom.xml

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-rest-csrf</artifactId>
    </dependency>

build.gradle

    implementation("io.quarkus:quarkus-rest-csrf")

Next, let’s add a `csrfToken.html` Qute template producing an HTML form in the `src/main/resources/templates` folder:

    <!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.

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

    package io.quarkus.it.csrf;

    import jakarta.inject.Inject;
    import jakarta.ws.rs.Consumes;
    import jakarta.ws.rs.FormParam;
    import jakarta.ws.rs.GET;
    import jakarta.ws.rs.POST;
    import jakarta.ws.rs.Path;
    import jakarta.ws.rs.Produces;
    import jakarta.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 userName) {
            return userName; (3)
        }
    }

1 | Inject the `csrfToken.html` as a `Template`.
2 | Return the HTML form with a hidden form field containing a CSRF token created by the CSRF filter.
3 | Handle the POST form request, this method can only be invoked 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 you can change these names if you would like:

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

## Sign CSRF token

You can get `HMAC` signatures created for the generated CSRF tokens and have these `HMAC` values stored as CSRF token cookies if you would like to avoid the risk of the attackers recreating the CSRF cookie token. All you need to do is to configure a token signature secret which must be at least 32 characters long:

    quarkus.rest-csrf.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

## CSRF Request Header

If HTML `form` tags are not used and you need to pass CSRF token as a header, then inject the header name and token, for example, into HTMX:

    <body hx-headers='{"{inject:csrf.headerName}":"{inject:csrf.token}"}'> (1)
    </body>

1 | This expression is used to inject a CSRF token header and token. This token will be verified by the CSRF filter against a CSRF cookie.

Default header name is `X-CSRF-TOKEN`, you can customize it with `quarkus.rest-csrf.token-header-name`, for example:

    quarkus.rest-csrf.token-header-name=CUSTOM-X-CSRF-TOKEN

If you need to access the CSRF cookie from JavaScript in order to pass its value as a header, use `{inject:csrf.cookieName}` and `{inject:csrf.headerName}` to inject the cookie name which has to be read as a CSRF header value and allow accessing this cookie:

    quarkus.rest-csrf.cookie-http-only=false

## Cross-origin resource sharing

| If you would like to enforce CSRF prevention in a Cross-origin environment, please avoid supporting all Origins. Restrict supported Origins to trusted Origins only, see CORS filter section of the "Cross-origin resource sharing" guide for more information.

## Restrict CSRF token verification

Your Jakarta REST endpoint may accept not only HTTP POST requests with `application/x-www-form-urlencoded` or `multipart/form-data` payloads but also payloads with other media types, either on the same or different URL paths, and therefore you would like to avoid verifying the CSRF token in such cases, for example:

    package io.quarkus.it.csrf;

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

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

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

        @Inject
        Template csrfToken;

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

        (1)
        @POST
        @Path("/user")
        @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
        @Produces(MediaType.TEXT_PLAIN)
        public String postCsrfTokenForm(@FormParam("name") String userName) {
            return userName;
        }

        (2)
        @POST
        @Path("/user")
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.TEXT_PLAIN)
        public String postJson(User user) {
            return user.name;
        }

        (3)
        @POST
        @Path("/users")
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.TEXT_PLAIN)
        public String postJson(User user) {
            return user.name;
        }

        public static class User {
            private String name;
            public String getName() {
                return this.name;
            }
            public void setName(String name) {
                this.name = name;
            }
        }
    }

1 | POST form request to `/user`, CSRF token verification is enforced by the CSRF filter
2 | POST json request to `/user`, CSRF token verification is not needed
3 | POST json request to `/users`, CSRF token verification is not needed

As you can see a CSRF token verification will be required at the `/service/user` path accepting the `application/x-www-form-urlencoded` payload, but `User` JSON representation posted to both `/service/user` and `/service/users` method will have no CSRF token and therefore the token verification has to be skipped in these cases by restricting it to the specific `/service/user` request path but also allowing not only `application/x-www-form-urlencoded` on this path:

    # Verify CSRF token only for the `/service/user` path, ignore other paths such as `/service/users`
    quarkus.rest-csrf.create-token-path=/service/user

    # If `/service/user` path accepts not only `application/x-www-form-urlencoded` payloads but also other ones such as JSON then allow them
    # Setting this property is not necessary when the token is submitted as a header value
    quarkus.rest-csrf.require-form-url-encoded=false

## Verify CSRF token in the application code

If you prefer to compare the CSRF form field and cookie values in the application code then you can do it as follows:

    package io.quarkus.it.csrf;

    import jakarta.inject.Inject;
    import jakarta.ws.rs.BadRequestException;
    import jakarta.ws.rs.Consumes;
    import jakarta.ws.rs.CookieParam;
    import jakarta.ws.rs.FormParam;
    import jakarta.ws.rs.GET;
    import jakarta.ws.rs.POST;
    import jakarta.ws.rs.Path;
    import jakarta.ws.rs.Produces;
    import jakarta.ws.rs.core.Cookie;
    import jakarta.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") Cookie 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.rest-csrf.verify-token=false

## Configuration Reference

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

Configuration property | Type | Default
--- | --- | ---
`quarkus.rest-csrf.enabled` If filter is enabled. Environment variable: `QUARKUS_REST_CSRF_ENABLED` | boolean | `true`
`quarkus.rest-csrf.form-field-name` Form field name which keeps a CSRF token. Environment variable: `QUARKUS_REST_CSRF_FORM_FIELD_NAME` | string | `csrf-token`
`quarkus.rest-csrf.token-header-name` Token header which can provide a CSRF token. Environment variable: `QUARKUS_REST_CSRF_TOKEN_HEADER_NAME` | string | `X-CSRF-TOKEN`
`quarkus.rest-csrf.cookie-name` CSRF cookie name. Environment variable: `QUARKUS_REST_CSRF_COOKIE_NAME` | string | `csrf-token`
`quarkus.rest-csrf.cookie-max-age` CSRF cookie max age. Environment variable: `QUARKUS_REST_CSRF_COOKIE_MAX_AGE` | Duration | `2H`
`quarkus.rest-csrf.cookie-path` CSRF cookie path. Environment variable: `QUARKUS_REST_CSRF_COOKIE_PATH` | string | `/`
`quarkus.rest-csrf.cookie-domain` CSRF cookie domain. Environment variable: `QUARKUS_REST_CSRF_COOKIE_DOMAIN` | string |
`quarkus.rest-csrf.cookie-force-secure` 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_REST_CSRF_COOKIE_FORCE_SECURE` | boolean | `false`
`quarkus.rest-csrf.cookie-http-only` Set the HttpOnly attribute to prevent access to the cookie via JavaScript. Environment variable: `QUARKUS_REST_CSRF_COOKIE_HTTP_ONLY` | boolean | `true`
`quarkus.rest-csrf.create-token-path` Create CSRF token only if the HTTP GET relative request path matches one of the paths configured with this property. Use a comma to separate multiple path values. Environment variable: `QUARKUS_REST_CSRF_CREATE_TOKEN_PATH` | list of string |
`quarkus.rest-csrf.token-size` Random CSRF token size in bytes. Environment variable: `QUARKUS_REST_CSRF_TOKEN_SIZE` | int | `16`
`quarkus.rest-csrf.token-signature-key` CSRF token HMAC signature key, if this key is set then it must be at least 32 characters long. Environment variable: `QUARKUS_REST_CSRF_TOKEN_SIGNATURE_KEY` | string |
`quarkus.rest-csrf.verify-token` Verify CSRF token in the CSRF filter. If you prefer then you can disable this property and compare CSRF form and cookie parameters in the application code using JAX-RS jakarta.ws.rs.FormParam which refers to the `form-field-name` form property and jakarta.ws.rs.CookieParam which refers to the `RestCsrfConfig#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 either 'application/x-www-form-urlencoded' or 'multipart/form-data'. Environment variable: `QUARKUS_REST_CSRF_VERIFY_TOKEN` | boolean | `true`
`quarkus.rest-csrf.require-form-url-encoded` Require that only 'application/x-www-form-urlencoded' or 'multipart/form-data' 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 and `token-header-name` is not configured. Environment variable: `QUARKUS_REST_CSRF_REQUIRE_FORM_URL_ENCODED` | boolean | `true`

| About the Duration format To write duration values, use the standard `java.time.Duration` format. See the Duration#parse() Java API documentation for more information. You can also use a simplified format, starting with a number: If the value is only a number, it represents time in seconds. If the value is a number followed by `ms`, it represents time in milliseconds. In other cases, the simplified format is translated to the `java.time.Duration` format for parsing: If the value is a number followed by `h`, `m`, or `s`, it is prefixed with `PT`. If the value is a number followed by `d`, it is prefixed with `P`.

## References

- OWASP Cross-Site Request Forgery

- Quarkus REST

- Qute Reference

- Cross-origin resource sharing

- Quarkus Security overview