Skip to content

Commit

Permalink
Update the request filter to return Uni<Response>
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Sep 7, 2022
1 parent 55ed99d commit 79d159e
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 48 deletions.
8 changes: 2 additions & 6 deletions docs/src/main/asciidoc/security-csrf-prevention.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,13 @@ import javax.ws.rs.core.MediaType;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.vertx.ext.web.RoutingContext;
@Path("/service")
public class UserNameResource {
@Inject
Template csrfToken; <1>
@Inject
RoutingContext routingContext;
@GET
@Path("/csrfTokenForm")
@Produces(MediaType.TEXT_HTML)
Expand All @@ -112,14 +108,14 @@ public class UserNameResource {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_PLAIN)
public String postCsrfTokenForm(@FormParam("name") String name) {
return userName + ":" + routingContext.get("csrf_token_verified", false); <3>
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, if this method is invoked then the CSRF filter has successfully verified the token - if necessary you can confirm it by checking a `RoutingContext` `csrf_token_verified` attribute is being set to `true`.
<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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.MediaType;
Expand Down Expand Up @@ -68,13 +66,28 @@ public CsrfRequestResponseReactiveFilter() {
* </ul>
*/
@ServerRequestFilter(preMatching = true)
public Uni<Void> filter(ContainerRequestContext requestContext, RoutingContext routing) {
Optional<String> cookieToken = getCookieToken(routing);
cookieToken.ifPresent(token -> routing.put(CSRF_TOKEN_KEY, token));
public Uni<Response> filter(ContainerRequestContext requestContext, RoutingContext routing) {
String cookieToken = getCookieToken(routing);
if (cookieToken != null) {
routing.put(CSRF_TOKEN_KEY, cookieToken);

try {
int suppliedTokenSize = Base64.getUrlDecoder().decode(cookieToken).length;

if (suppliedTokenSize != config.get().tokenSize) {
LOG.debugf("Invalid CSRF token cookie size: expected %d, got %d", config.get().tokenSize,
suppliedTokenSize);
return Uni.createFrom().item(badClientRequest());
}
} catch (IllegalArgumentException e) {
LOG.debugf("Invalid CSRF token cookie: %s", cookieToken);
return Uni.createFrom().item(badClientRequest());
}
}

if (requestMethodIsSafe(requestContext)) {
// safe HTTP method, tolerate the absence of a token
if (cookieToken.isEmpty()) {
if (cookieToken == null) {
// Set the CSRF cookie with a randomly generated value
byte[] token = new byte[config.get().tokenSize];
new SecureRandom().nextBytes(token);
Expand All @@ -84,47 +97,52 @@ public Uni<Void> filter(ContainerRequestContext requestContext, RoutingContext r
// unsafe HTTP method, token is required
if (!requestContext.hasEntity()) {
LOG.debug("Request has no entity");
requestContext.abortWith(Response.status(400).build());
return null;
return Uni.createFrom().item(badClientRequest());
}

if (!requestContext.getMediaType().getType().equals(MediaType.APPLICATION_FORM_URLENCODED_TYPE.getType())
|| !requestContext.getMediaType().getSubtype()
.equals(MediaType.APPLICATION_FORM_URLENCODED_TYPE.getSubtype())) {
LOG.debugf("Request has the wrong media type: %s", requestContext.getMediaType().toString());
requestContext.abortWith(Response.status(400).build());
return null;
return Uni.createFrom().item(badClientRequest());
}

final String expectedToken = cookieToken.orElseThrow(() -> new BadRequestException("Invalid CSRF token"));
if (cookieToken == null) {
LOG.debug("CSRF cookie is not found");
return Uni.createFrom().item(badClientRequest());
}

return getFormUrlEncodedData(routing.request())
.flatMap(new Function<MultiMap, Uni<? extends Void>>() {
.flatMap(new Function<MultiMap, Uni<? extends Response>>() {
@Override
public Uni<Void> apply(MultiMap form) {
public Uni<Response> apply(MultiMap form) {

String csrfToken = form.get(config.get().formFieldName);
if (csrfToken == null) {
LOG.debug("CSRF token is not found");
requestContext.abortWith(Response.status(400).build());
} else if (!csrfToken.equals(expectedToken)) {
return Uni.createFrom().item(badClientRequest());
} else if (!csrfToken.equals(cookieToken)) {
LOG.debug("CSRF token value is wrong");
requestContext.abortWith(Response.status(400).build());
return Uni.createFrom().item(badClientRequest());
} else {
routing.put(CSRF_TOKEN_VERIFIED, true);
requestContext.setEntityStream(new ByteArrayInputStream(encodeForm(form).getBytes()));
}
return Uni.createFrom().voidItem();
return Uni.createFrom().nullItem();
}
});
} else if (cookieToken.isEmpty()) {
} else if (cookieToken == null) {
LOG.debug("CSRF token is not found");
requestContext.abortWith(Response.status(400).build());
return Uni.createFrom().item(badClientRequest());
}

return null;
}

private static Response badClientRequest() {
return Response.status(400).build();
}

/**
* If the requirements below are true, sets a cookie by the name {@value #CSRF_TOKEN_KEY} that contains a CSRF token.
* <ul>
Expand All @@ -138,7 +156,7 @@ public Uni<Void> apply(MultiMap form) {
@ServerResponseFilter
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext, RoutingContext routing) {
if (requestContext.getMethod().equals("GET") && getCookieToken(routing).isEmpty()) {
if (requestContext.getMethod().equals("GET") && getCookieToken(routing) == null) {
String token = (String) routing.get(CSRF_TOKEN_KEY);

if (token == null) {
Expand All @@ -156,34 +174,15 @@ public void filter(ContainerRequestContext requestContext,
*
* @return An Optional containing the token, or an empty Optional if the token cookie is not present or is invalid
*/
private Optional<String> getCookieToken(RoutingContext routing) {
private String getCookieToken(RoutingContext routing) {
Cookie cookie = routing.getCookie(config.get().cookieName);

if (cookie == null) {
LOG.debug("CSRF token cookie is not set");
return Optional.empty();
}

if (cookie.getValue() == null) {
LOG.debug("CSRF token cookie value is not set");
throw new BadRequestException("Invalid CSRF token");
}

String token = cookie.getValue();

try {
int suppliedTokenSize = Base64.getUrlDecoder().decode(token).length;

if (suppliedTokenSize != config.get().tokenSize) {
LOG.debugf("Invalid CSRF token cookie size: expected %d, got %d", config.get().tokenSize, suppliedTokenSize);
throw new BadRequestException("Invalid CSRF token");
}
} catch (IllegalArgumentException e) {
LOG.debugf("Invalid CSRF token cookie: %s", token);
throw new BadRequestException("Invalid CSRF token");
return null;
}

return Optional.of(token);
return cookie.getValue();
}

private void createCookie(String csrfToken, RoutingContext routing) {
Expand Down

0 comments on commit 79d159e

Please sign in to comment.