All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.susom.vertx.base.WebAppJwtAuthHandler Maven / Gradle / Ivy

/*
 * Copyright 2016 The Board of Trustees of The Leland Stanford Junior University.
 * All Rights Reserved.
 *
 * See the NOTICE and LICENSE files distributed with this work for information
 * regarding copyright ownership and licensing. You may not use this file except
 * in compliance with a written license agreement with Stanford University.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See your
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.github.susom.vertx.base;

import com.github.susom.database.Metric;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.web.Cookie;
import io.vertx.ext.web.RoutingContext;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import static io.vertx.core.http.HttpHeaders.SET_COOKIE;

/**
 * This handler uses JWT tokens from a browser cookie ("access_token")
 * to authenticate the user. It supports both optional and mandatory
 * authentication, so you can put an optional one in front of everything
 * to get attributed logging where possible, and a mandatory handler
 * in front of your protected resources.
 *
 * 

This handler manages the SLF4J MDC context by populating the "userId" * (based on authentication) and "windowId" (based on the X-WINDOW-ID header, * if present). These are generally not cleared by this handler, as that is * left to the MetricsHandler after it finishes logging the response.

* *

If enforcement is mandatory, a 401 response will be returned if * authentication did not succeed for any reason.

* *

If enforcement is mandatory, it will also perform XSRF defenses, * matching the cookie named XSRF-TOKEN (if present) against the header * named X-XSRF-TOKEN. A 403 response will be returned if the header * was not provided or did not match the cookie.

*/ public class WebAppJwtAuthHandler implements Handler { private static final Logger log = LoggerFactory.getLogger(WebAppJwtAuthHandler.class); private final JWTAuth jwt; private final boolean mandatory; private final Handler redirecter; private WebAppJwtAuthHandler(JWTAuth jwt, boolean mandatory, Handler redirecter) { this.jwt = jwt; this.mandatory = mandatory; this.redirecter = redirecter; } public static WebAppJwtAuthHandler optional(JWTAuth jwt) { return new WebAppJwtAuthHandler(jwt, false, null); } public static WebAppJwtAuthHandler mandatory(JWTAuth jwt) { return new WebAppJwtAuthHandler(jwt, true, null); } public static WebAppJwtAuthHandler mandatory(JWTAuth jwt, Handler redirectUri) { return new WebAppJwtAuthHandler(jwt, true, redirectUri); } public void handle(RoutingContext rc) { AuthenticatedUser user = AuthenticatedUser.from(rc); if (user != null) { String userId = user.getAuthenticatedAs(); if (!userId.equals(MDC.get("userId"))) { log.warn("User from routing context (" + userId + ") did not match logging context (" + MDC.get("userId") + ")"); MDC.put("userId", userId); } rc.next(); } else { MDC.remove("userId"); String windowId = rc.request().getHeader("X-WINDOW-ID"); if (windowId != null && windowId.matches("[a-zA-Z0-9]{1,32}")) { MDC.put("windowId", windowId); } else { MDC.remove("windowId"); } if (mandatory) { Cookie xsrf = rc.getCookie("XSRF-TOKEN"); if (xsrf != null) { String xsrfHeader = rc.request().getHeader("X-XSRF-TOKEN"); if (xsrfHeader == null || xsrfHeader.length() == 0) { log.debug("Missing XSRF header"); if (redirecter == null) { rc.response().setStatusCode(403).end("Send X-XSRF-TOKEN header with value from XSRF-TOKEN cookie"); } else { redirecter.handle(rc); } return; } else if (!xsrf.getValue().equals(xsrfHeader)) { log.debug("XSRF header did not match"); if (redirecter == null) { rc.response().setStatusCode(403).end("The X-XSRF-TOKEN header value did not match the XSRF-TOKEN cookie"); } else { redirecter.handle(rc); } return; } } } Cookie session = rc.getCookie("access_token"); if (session != null && session.getValue() != null) { jwt.authenticate(new JsonObject().put("jwt", session.getValue()), r -> { if (r.succeeded()) { MetricsHandler.checkpoint(rc, "auth"); String userId = r.result().principal().getString("sub"); String displayName = r.result().principal().getString("name"); new AuthenticatedUser(userId, userId, displayName, new HashSet<>()).store(rc); MDC.put("userId", userId); rc.next(); } else { MetricsHandler.checkpoint(rc, "authFail"); rc.response().headers().add(SET_COOKIE, session.setValue("").setMaxAge(0).encode()); if (mandatory) { log.debug("Access token could not be authenticated", r.cause()); if (redirecter == null) { rc.response().setStatusCode(401).end("Access token expired"); } else { redirecter.handle(rc); } } else { rc.next(); } } }); } else { MetricsHandler.checkpoint(rc, "noAuth"); if (mandatory) { if (redirecter == null) { rc.response().setStatusCode(401).end("No access_token cookie"); } else { redirecter.handle(rc); } } else { rc.next(); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy