logout(String redirectUri) {
+ Objects.requireNonNull(redirectUri, "redirectUri cannot be null");
+
+ final Session session = ctx.session();
+ // clear the session
+ if (session != null) {
+ session.destroy();
+ }
+
+ // clear the user
+ user = null;
+
+ // we should redirect the UA so this link becomes invalid
+ return ctx.response()
+ // disable all caching
+ .putHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
+ .putHeader("Pragma", "no-cache")
+ .putHeader(HttpHeaders.EXPIRES, "0")
+ // redirect (when there is no state, redirect to home
+ .putHeader(HttpHeaders.LOCATION, redirectUri)
+ .setStatusCode(302)
+ .end("Redirecting to " + redirectUri + ".");
+ }
+
+ @Override
+ public void clear() {
+ final Session session = ctx.session();
+ // clear the session
+ if (session != null) {
+ session.destroy();
+ }
+
+ // clear the user
+ user = null;
+ }
+}
diff --git a/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationContext.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationContext.java
new file mode 100644
index 0000000000..32e3a154b6
--- /dev/null
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationContext.java
@@ -0,0 +1,42 @@
+package io.vertx.ext.auth.common;
+
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpServerResponse;
+
+/**
+ * Context that is being accepted by various authentication handlers.
+ *
+ * The context allows access to the HTTP request and response to verify provided authentication information
+ *
+ * The {@link UserContext} provides access to the authenticated user.
+ *
+ */
+public interface AuthenticationContext {
+
+ /**
+ * @return the HTTP request object
+ */
+ HttpServerRequest request();
+
+ /**
+ * @return the HTTP response object
+ */
+ HttpServerResponse response();
+
+ /**
+ * Control the user associated with this request. The user context allows accessing the security user object as well as perform authentication refreshes,
+ * logout and other operations.
+ *
+ * @return the user context
+ */
+ UserContext user();
+
+ String normalizedPath();
+
+ default void onContinue() {
+ // NOOP
+ }
+
+ Session session();
+
+}
diff --git a/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationContextInternal.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationContextInternal.java
new file mode 100644
index 0000000000..c2778f326b
--- /dev/null
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationContextInternal.java
@@ -0,0 +1,17 @@
+package io.vertx.ext.auth.common;
+
+import io.vertx.ext.auth.audit.SecurityAudit;
+
+public interface AuthenticationContextInternal {
+
+ /**
+ * Get or Default the security audit object.
+ */
+ SecurityAudit securityAudit();
+
+ /**
+ * Get or Default the security audit object.
+ */
+ void setSecurityAudit(SecurityAudit securityAudit);
+
+}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/AuthenticationHandler.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationHandler.java
similarity index 63%
rename from vertx-web/src/main/java/io/vertx/ext/web/handler/AuthenticationHandler.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationHandler.java
index 4fa8a1e720..75de609417 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/AuthenticationHandler.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/AuthenticationHandler.java
@@ -14,22 +14,16 @@
* You may elect to redistribute this code under either of these licenses.
*/
-package io.vertx.ext.web.handler;
+package io.vertx.ext.auth.common;
-import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Handler;
-import io.vertx.ext.web.RoutingContext;
/**
* Base interface for auth handlers.
- *
- * An auth handler allows your application to provide authentication support.
- *
- * An Auth handler may require a {@link SessionHandler} to be on the routing chain before it.
*
* @author Tim Fox
* @author Paulo Lopes
*/
-@VertxGen(concrete = false)
-public interface AuthenticationHandler extends Handler {
+public interface AuthenticationHandler extends Handler {
+
}
diff --git a/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/ScopedAuthentication.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/ScopedAuthentication.java
new file mode 100644
index 0000000000..2960379c2b
--- /dev/null
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/ScopedAuthentication.java
@@ -0,0 +1,37 @@
+package io.vertx.ext.auth.common;
+
+import java.util.List;
+
+/**
+ * Internal interface for scope aware Authentication handlers.
+ *
+ * @param
+ * @author Paulo Lopes
+ */
+public interface ScopedAuthentication> {
+
+ /**
+ * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token request are unique to the instance.
+ *
+ * @param scope
+ * scope.
+ * @return new instance of this interface.
+ */
+ SELF withScope(String scope);
+
+ /**
+ * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token request are unique to the instance.
+ *
+ * @param scopes
+ * scopes.
+ * @return new instance of this interface.
+ */
+ SELF withScopes(List scopes);
+
+ /**
+ * Return the list of scopes provided as the 1st argument, unless the list is empty. In this case, the list of scopes is obtained from the routing context
+ * metadata if possible. In case the metadata is not available, the list of scopes is always an empty list.
+ */
+ List getScopesOrSearchMetadata(List scopes, C ctx);
+
+}
diff --git a/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/Session.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/Session.java
new file mode 100644
index 0000000000..cd5d44b536
--- /dev/null
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/Session.java
@@ -0,0 +1,140 @@
+package io.vertx.ext.auth.common;
+
+import io.vertx.codegen.annotations.Fluent;
+import io.vertx.codegen.annotations.GenIgnore;
+import io.vertx.codegen.annotations.VertxGen;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Represents a browser session.
+ *
+ * Sessions persist between HTTP requests for a single browser session. They are deleted when the browser is closed, or
+ * they time-out. Session cookies are used to maintain sessions using a secure UUID.
+ *
+ * Sessions can be used to maintain data for a browser session, e.g. a shopping basket.
+ *
+ * The context must have first been routed to a {@link io.vertx.ext.web.handler.SessionHandler}
+ * for sessions to be available.
+ *
+ * @author Tim Fox
+ */
+@VertxGen
+public interface Session {
+
+ /**
+ * @return The new unique ID of the session.
+ */
+ Session regenerateId();
+
+ /**
+ * @return The unique ID of the session. This is generated using a random secure UUID.
+ */
+ String id();
+
+ /**
+ * Put some data in a session
+ *
+ * @param key the key for the data
+ * @param obj the data
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ Session put(String key, Object obj);
+
+ /**
+ * Put some data in a session if absent
+ *
+ * @param key the key for the data
+ * @param obj the data
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ Session putIfAbsent(String key, Object obj);
+
+ /**
+ * Put some data in a session if absent.
+ *
+ * If the specified key is not already associated with a value (or is mapped
+ * to {@code null}), attempts to compute its value using the given mapping
+ * function and enters it into this map unless {@code null}.
+ *
+ * @param key the key for the data
+ * @param mappingFunction a mapping function
+ * @return a reference to this, so the API can be used fluently
+ */
+ @Fluent
+ Session computeIfAbsent(String key, Function mappingFunction);
+
+ /**
+ * Get some data from the session
+ *
+ * @param key the key of the data
+ * @return the data
+ */
+ T get(String key);
+
+ /**
+ * Remove some data from the session
+ *
+ * @param key the key of the data
+ * @return the data that was there or null if none there
+ */
+ T remove(String key);
+
+ /**
+ * @return the session data as a map
+ */
+ @GenIgnore(GenIgnore.PERMITTED_TYPE)
+ Map data();
+
+ /**
+ * @return true if the session has data
+ */
+ boolean isEmpty();
+
+ /**
+ * @return the time the session was last accessed
+ */
+ long lastAccessed();
+
+ /**
+ * Destroy the session
+ */
+ void destroy();
+
+ /**
+ * @return has the session been destroyed?
+ */
+ boolean isDestroyed();
+
+ /**
+ * @return has the session been renewed?
+ */
+ boolean isRegenerated();
+
+ /**
+ * @return old ID if renewed
+ */
+ String oldId();
+
+ /**
+ * @return the amount of time in ms, after which the session will expire, if not accessed.
+ */
+ long timeout();
+
+ /**
+ * Mark the session as being accessed.
+ */
+ void setAccessed();
+
+ /**
+ * The short representation of the session to be added to the session cookie. By default is the session id.
+ *
+ * @return short representation string.
+ */
+ default String value() {
+ return id();
+ }
+}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/UserContext.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/UserContext.java
similarity index 99%
rename from vertx-web/src/main/java/io/vertx/ext/web/UserContext.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/UserContext.java
index ad729d3893..5694598602 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/UserContext.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/UserContext.java
@@ -13,7 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
-package io.vertx.ext.web;
+package io.vertx.ext.auth.common;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.Nullable;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextInternal.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/UserContextInternal.java
similarity index 80%
rename from vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextInternal.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/UserContextInternal.java
index 391f3dbb7e..98f0dd701f 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextInternal.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/UserContextInternal.java
@@ -1,7 +1,6 @@
-package io.vertx.ext.web.impl;
+package io.vertx.ext.auth.common;
import io.vertx.ext.auth.User;
-import io.vertx.ext.web.UserContext;
public interface UserContextInternal extends UserContext {
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthenticationHandlerInternal.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/AuthenticationHandlerInternal.java
similarity index 68%
rename from vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthenticationHandlerInternal.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/AuthenticationHandlerInternal.java
index 435d2ed9f4..7f1a2915ef 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthenticationHandlerInternal.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/AuthenticationHandlerInternal.java
@@ -1,21 +1,21 @@
-package io.vertx.ext.web.handler.impl;
+package io.vertx.ext.auth.common.handler;
import io.vertx.core.Future;
import io.vertx.ext.auth.User;
-import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.handler.AuthenticationHandler;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.AuthenticationHandler;
-public interface AuthenticationHandlerInternal extends AuthenticationHandler {
+public interface AuthenticationHandlerInternal extends AuthenticationHandler {
/**
* Parses the credentials from the request into a JsonObject. The implementation should
* be able to extract the required info for the auth provider in the format the provider
* expects.
*
- * @param context the routing context
+ * @param context the authentication context
* @return future user to be called once the information is available.
*/
- Future authenticate(RoutingContext context);
+ Future authenticate(C context);
/**
* Applies a {@code WWW-Authenticate} Response Header.
@@ -24,10 +24,10 @@ public interface AuthenticationHandlerInternal extends AuthenticationHandler {
* acceptable Authorization header is not sent, the server responds with
* a "401 Unauthorized" status code, and a WWW-Authenticate header.
*
- * @param context the routing context
+ * @param context the authentication context
* @return the {@code true} if a header was added.
*/
- default boolean setAuthenticateHeader(RoutingContext context) {
+ default boolean setAuthenticateHeader(C context) {
return false;
}
@@ -36,10 +36,11 @@ default boolean setAuthenticateHeader(RoutingContext context) {
* Overrides must call {@link RoutingContext#next()} on success. Implementations must call this handler
* at the end of the authentication process.
*
- * @param ctx the routing context
+ * @param ctx the authentication context
+ * @param authenticated the authenticated user
*/
- default void postAuthentication(RoutingContext ctx) {
- ctx.next();
+ default void postAuthentication(C ctx, User authenticated) {
+ ctx.onContinue();
}
/**
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthenticationHandlerImpl.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/impl/AuthenticationHandlerImpl.java
similarity index 65%
rename from vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthenticationHandlerImpl.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/impl/AuthenticationHandlerImpl.java
index a6570e71fb..c4923aaa25 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthenticationHandlerImpl.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/impl/AuthenticationHandlerImpl.java
@@ -14,26 +14,22 @@
* You may elect to redistribute this code under either of these licenses.
*/
-package io.vertx.ext.web.handler.impl;
+package io.vertx.ext.auth.common.handler.impl;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
-import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.Session;
-import io.vertx.ext.web.handler.HttpException;
-import io.vertx.ext.web.impl.UserContextInternal;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.Session;
+import io.vertx.ext.auth.common.UserContextInternal;
+import io.vertx.ext.auth.common.handler.AuthenticationHandlerInternal;
/**
* @author Tim Fox
*/
-public abstract class AuthenticationHandlerImpl implements AuthenticationHandlerInternal {
-
- static final HttpException UNAUTHORIZED = new HttpException(401);
- static final HttpException BAD_REQUEST = new HttpException(400);
- static final HttpException BAD_METHOD = new HttpException(405);
+public abstract class AuthenticationHandlerImpl implements AuthenticationHandlerInternal {
protected final T authProvider;
// signal the kind of Multi-Factor Authentication used by the handler
@@ -48,8 +44,7 @@ public AuthenticationHandlerImpl(T authProvider, String mfa) {
this.mfa = mfa;
}
- @Override
- public void handle(RoutingContext ctx) {
+ public void handle(C ctx) {
if (handlePreflight(ctx)) {
return;
@@ -70,7 +65,7 @@ public void handle(RoutingContext ctx) {
if (!ctx.request().isEnded()) {
ctx.request().resume();
}
- postAuthentication(ctx);
+ postAuthentication(ctx, user);
return;
}
} else {
@@ -78,7 +73,7 @@ public void handle(RoutingContext ctx) {
if (!ctx.request().isEnded()) {
ctx.request().resume();
}
- postAuthentication(ctx);
+ postAuthentication(ctx, user);
return;
}
}
@@ -97,7 +92,7 @@ public void handle(RoutingContext ctx) {
if (!ctx.request().isEnded()) {
ctx.request().resume();
}
- postAuthentication(ctx);
+ postAuthentication(ctx, authenticated);
})
.onFailure(cause -> {
// to allow further processing if needed
@@ -108,41 +103,9 @@ public void handle(RoutingContext ctx) {
});
}
- /**
- * This method is protected so custom auth handlers can override the default
- * error handling
- */
- protected void processException(RoutingContext ctx, Throwable exception) {
- if (exception != null) {
- if (exception instanceof HttpException) {
- final int statusCode = ((HttpException) exception).getStatusCode();
- final String payload = ((HttpException) exception).getPayload();
-
- switch (statusCode) {
- case 302:
- ctx.response()
- .putHeader(HttpHeaders.LOCATION, payload)
- .setStatusCode(302)
- .end("Redirecting to " + payload + ".");
- return;
- case 401:
- if (!"XMLHttpRequest".equals(ctx.request().getHeader("X-Requested-With"))) {
- setAuthenticateHeader(ctx);
- }
- ctx.fail(401, exception);
- return;
- default:
- ctx.fail(statusCode, exception);
- return;
- }
- }
- }
-
- // fallback 500
- ctx.fail(exception);
- }
+ protected abstract void processException(C ctx, Throwable cause);
- private boolean handlePreflight(RoutingContext ctx) {
+ private boolean handlePreflight(C ctx) {
final HttpServerRequest request = ctx.request();
// See: https://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
// Preflight requests should not be subject to security due to the reason UAs will remove the Authorization header
@@ -154,7 +117,7 @@ private boolean handlePreflight(RoutingContext ctx) {
for (String ctrlReq : accessControlRequestHeader.split(",")) {
if (ctrlReq.equalsIgnoreCase("Authorization")) {
// this request has auth in access control, so we can allow preflighs without authentication
- ctx.next();
+ ctx.onContinue();
return true;
}
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HTTPAuthorizationHandler.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/impl/HTTPAuthorizationHandler.java
similarity index 85%
rename from vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HTTPAuthorizationHandler.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/impl/HTTPAuthorizationHandler.java
index 199719380b..617844f78e 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HTTPAuthorizationHandler.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/auth/common/handler/impl/HTTPAuthorizationHandler.java
@@ -13,20 +13,22 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
-package io.vertx.ext.web.handler.impl;
+package io.vertx.ext.auth.common.handler.impl;
import io.vertx.core.Future;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
-import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.auth.common.AuthenticationContext;
+
+import static io.vertx.ext.web.handler.HttpException.*;
/**
* This a common handler for auth handler that use the `Authorization` HTTP header.
*
* @author Paulo Lopes
*/
-public abstract class HTTPAuthorizationHandler extends AuthenticationHandlerImpl {
+public abstract class HTTPAuthorizationHandler extends AuthenticationHandlerImpl {
// this should match the IANA registry: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
public enum Type {
@@ -73,11 +75,11 @@ public HTTPAuthorizationHandler(T authProvider, Type type, String realm) {
}
}
- protected final Future parseAuthorization(RoutingContext ctx) {
+ protected final Future parseAuthorization(C ctx) {
return parseAuthorization(ctx, false);
}
- protected final Future parseAuthorization(RoutingContext ctx, boolean optional) {
+ protected final Future parseAuthorization(C ctx, boolean optional) {
final HttpServerRequest request = ctx.request();
final String authorization = request.headers().get(HttpHeaders.AUTHORIZATION);
@@ -109,7 +111,7 @@ protected final Future parseAuthorization(RoutingContext ctx, boolean op
}
@Override
- public boolean setAuthenticateHeader(RoutingContext context) {
+ public boolean setAuthenticateHeader(C context) {
if (realm != null && realm.length() > 0) {
context.response()
.headers()
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/HttpException.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/web/common/HttpException.java
similarity index 95%
rename from vertx-web/src/main/java/io/vertx/ext/web/handler/HttpException.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/web/common/HttpException.java
index 562dea19d0..1dcb6fa537 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/HttpException.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/web/common/HttpException.java
@@ -13,7 +13,7 @@
*
* You may elect to redistribute this code under either of these licenses.
*/
-package io.vertx.ext.web.handler;
+package io.vertx.ext.web.common;
import io.netty.handler.codec.http.HttpResponseStatus;
@@ -28,7 +28,7 @@
*
* @author Paulo Lopes
*/
-public final class HttpException extends RuntimeException {
+public abstract class HttpException extends RuntimeException {
private final int statusCode;
private final String payload;
diff --git a/vertx-web-auth-common/src/main/java/io/vertx/ext/web/handler/HttpException.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/web/handler/HttpException.java
new file mode 100644
index 0000000000..b80ac25ca1
--- /dev/null
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/web/handler/HttpException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 Red Hat, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * The Apache License v2.0 is available at
+ * http://www.opensource.org/licenses/apache2.0.php
+ *
+ * You may elect to redistribute this code under either of these licenses.
+ */
+package io.vertx.ext.web.handler;
+
+/**
+ * @see io.vertx.ext.web.common.HttpException
+ */
+public final class HttpException extends io.vertx.ext.web.common.HttpException {
+
+ public static final HttpException UNAUTHORIZED = new HttpException(401);
+ public static final HttpException BAD_REQUEST = new HttpException(400);
+ public static final HttpException BAD_METHOD = new HttpException(405);
+
+ public HttpException() {
+ super();
+ }
+
+ public HttpException(int statusCode) {
+ super(statusCode);
+ }
+
+ public HttpException(int statusCode, Throwable cause) {
+ super(statusCode, cause);
+ }
+
+ public HttpException(int statusCode, String payload) {
+ super(statusCode, payload);
+ }
+
+ public HttpException(int statusCode, String payload, Throwable cause) {
+ super(statusCode, payload, cause);
+ }
+
+}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/Origin.java b/vertx-web-auth-common/src/main/java/io/vertx/ext/web/impl/Origin.java
similarity index 98%
rename from vertx-web/src/main/java/io/vertx/ext/web/impl/Origin.java
rename to vertx-web-auth-common/src/main/java/io/vertx/ext/web/impl/Origin.java
index c03fb6ba07..1256cc3572 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/Origin.java
+++ b/vertx-web-auth-common/src/main/java/io/vertx/ext/web/impl/Origin.java
@@ -19,7 +19,7 @@
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
-import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.auth.common.AuthenticationContext;
/**
* An origin follows rfc6454#section-7
@@ -422,7 +422,7 @@ private static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
- public static boolean check(Origin origin, RoutingContext ctx) {
+ public static boolean check(Origin origin, AuthenticationContext ctx) {
/* Verifying Same Origin with Standard Headers */
if (origin != null) {
//Try to get the source from the "Origin" header
diff --git a/vertx-web-auth-common/src/main/resources/META-INF/MANIFEST.MF b/vertx-web-auth-common/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..1eae9090c8
--- /dev/null
+++ b/vertx-web-auth-common/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Automatic-Module-Name: io.vertx.web.auth-common
+
diff --git a/vertx-web-auth-jwt/pom.xml b/vertx-web-auth-jwt/pom.xml
new file mode 100644
index 0000000000..c433fa0fd1
--- /dev/null
+++ b/vertx-web-auth-jwt/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ vertx-web-parent
+ io.vertx
+ 5.0.0-SNAPSHOT
+
+
+ vertx-web-auth-jwt
+
+
+ true
+
+
+
+
+ vertx-web-auth-common
+ io.vertx
+ 5.0.0-SNAPSHOT
+
+
+
+ vertx-auth-jwt
+ io.vertx
+ 5.0.0-SNAPSHOT
+
+
+
diff --git a/vertx-web-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/AbstractJWTHandler.java b/vertx-web-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/AbstractJWTHandler.java
new file mode 100644
index 0000000000..1aa23a8a14
--- /dev/null
+++ b/vertx-web-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/AbstractJWTHandler.java
@@ -0,0 +1,122 @@
+package io.vertx.ext.auth.jwt;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import io.vertx.core.Future;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.auth.User;
+import io.vertx.ext.auth.audit.Marker;
+import io.vertx.ext.auth.audit.SecurityAudit;
+import io.vertx.ext.auth.authentication.TokenCredentials;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.AuthenticationContextInternal;
+import io.vertx.ext.auth.common.handler.impl.HTTPAuthorizationHandler;
+import io.vertx.ext.web.handler.HttpException;
+
+public abstract class AbstractJWTHandler extends HTTPAuthorizationHandler implements JWTAuthHandler, io.vertx.ext.auth.common.ScopedAuthentication> {
+
+ protected final List scopes;
+ protected String delimiter;
+
+ public AbstractJWTHandler(JWTAuth authProvider, Type bearer, String realm) {
+ super(authProvider, bearer, realm);
+ scopes = Collections.emptyList();
+ this.delimiter = " ";
+ }
+
+ public AbstractJWTHandler(JWTAuth authProvider, List scopes,
+ String delimiter, String realm) {
+ super(authProvider, Type.BEARER, realm);
+ Objects.requireNonNull(scopes, "scopes cannot be null");
+ this.scopes = scopes;
+ Objects.requireNonNull(delimiter, "delimiter cannot be null");
+ this.delimiter = delimiter;
+ }
+
+ @Override
+ public Future authenticate(C context) {
+
+ return parseAuthorization(context)
+ .compose(token -> {
+ int segments = 0;
+ for (int i = 0; i < token.length(); i++) {
+ char c = token.charAt(i);
+ if (c == '.') {
+ if (++segments == 3) {
+ return Future.failedFuture(new HttpException(400, "Too many segments in token"));
+ }
+ continue;
+ }
+ if (Character.isLetterOrDigit(c) || c == '-' || c == '_') {
+ continue;
+ }
+ // invalid character
+ return Future.failedFuture(new HttpException(400, "Invalid character in token: " + (int) c));
+ }
+
+ final TokenCredentials credentials = new TokenCredentials(token);
+ final SecurityAudit audit = ((AuthenticationContextInternal) context).securityAudit();
+ audit.credentials(credentials);
+
+ return
+ authProvider
+ .authenticate(new TokenCredentials(token))
+ .andThen(op -> audit.audit(Marker.AUTHENTICATION, op.succeeded()))
+ .recover(err -> Future.failedFuture(new HttpException(401, err)));
+ });
+ }
+
+ /**
+ * The default behavior for post-authentication
+ */
+ @Override
+ public void postAuthentication(C ctx, User authenticated) {
+ final User user = ctx.user().get();
+ if (user == null) {
+ // bad state
+ fail(ctx, 403, "no user in the context");
+ return;
+ }
+ // the user is authenticated, however the user may not have all the required scopes
+ final List scopes = getScopesOrSearchMetadata(this.scopes, ctx);
+
+ if (scopes.size() > 0) {
+ final JsonObject jwt = user.get("accessToken");
+ if (jwt == null) {
+ fail(ctx, 403, "Invalid JWT: null");
+ return;
+ }
+
+ if (jwt.getValue("scope") == null) {
+ fail(ctx, 403, "Invalid JWT: scope claim is required");
+ return;
+ }
+
+ List> target;
+ if (jwt.getValue("scope") instanceof String) {
+ target =
+ Stream.of(jwt.getString("scope")
+ .split(delimiter))
+ .collect(Collectors.toList());
+ } else {
+ target = jwt.getJsonArray("scope").getList();
+ }
+
+ if (target != null) {
+ for (String scope : scopes) {
+ if (!target.contains(scope)) {
+ fail(ctx, 403, "JWT scopes != handler scopes");
+ return;
+ }
+ }
+ }
+ }
+ ctx.onContinue();
+ }
+
+ abstract protected void fail(C ctx, int code, String msg);
+}
diff --git a/vertx-web-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/JWTAuthHandler.java b/vertx-web-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/JWTAuthHandler.java
new file mode 100644
index 0000000000..22f7e4b6f2
--- /dev/null
+++ b/vertx-web-auth-jwt/src/main/java/io/vertx/ext/auth/jwt/JWTAuthHandler.java
@@ -0,0 +1,40 @@
+package io.vertx.ext.auth.jwt;
+
+import java.util.List;
+
+import io.vertx.codegen.annotations.Fluent;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.AuthenticationHandler;
+
+public interface JWTAuthHandler extends AuthenticationHandler {
+
+ /**
+ * Set the scope delimiter. By default this is a space character.
+ *
+ * @param delimiter
+ * scope delimiter.
+ * @return fluent self.
+ */
+ @Fluent
+ JWTAuthHandler scopeDelimiter(String delimiter);
+
+ /**
+ * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token request are unique to the instance. When
+ * scopes are applied to the handler, the default scopes from the route metadata will be ignored.
+ *
+ * @param scope
+ * scope.
+ * @return new instance of this interface.
+ */
+ JWTAuthHandler withScope(String scope);
+
+ /**
+ * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token request are unique to the instance. When
+ * scopes are applied to the handler, the default scopes from the route metadata will be ignored.
+ *
+ * @param scopes
+ * scopes.
+ * @return new instance of this interface.
+ */
+ JWTAuthHandler withScopes(List scopes);
+}
diff --git a/vertx-web-auth-jwt/src/main/resources/META-INF/MANIFEST.MF b/vertx-web-auth-jwt/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..85f2995cd1
--- /dev/null
+++ b/vertx-web-auth-jwt/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1 @@
+Automatic-Module-Name: io.vertx.web.auth-jwt
diff --git a/vertx-web-auth-oauth2/pom.xml b/vertx-web-auth-oauth2/pom.xml
new file mode 100644
index 0000000000..95accfcf5a
--- /dev/null
+++ b/vertx-web-auth-oauth2/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ vertx-web-parent
+ io.vertx
+ 5.0.0-SNAPSHOT
+
+
+ vertx-web-auth-oauth2
+
+
+ true
+
+
+
+
+ vertx-web-auth-common
+ io.vertx
+ 5.0.0-SNAPSHOT
+
+
+
+ vertx-auth-oauth2
+ io.vertx
+ 5.0.0-SNAPSHOT
+
+
+
diff --git a/vertx-web-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/AbstractOAuth2Handler.java b/vertx-web-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/AbstractOAuth2Handler.java
new file mode 100644
index 0000000000..550ee65752
--- /dev/null
+++ b/vertx-web-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/AbstractOAuth2Handler.java
@@ -0,0 +1,324 @@
+package io.vertx.ext.auth.oauth2;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.auth.User;
+import io.vertx.ext.auth.VertxContextPRNG;
+import io.vertx.ext.auth.audit.Marker;
+import io.vertx.ext.auth.audit.SecurityAudit;
+import io.vertx.ext.auth.authentication.Credentials;
+import io.vertx.ext.auth.authentication.TokenCredentials;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.AuthenticationContextInternal;
+import io.vertx.ext.auth.common.Session;
+import io.vertx.ext.auth.common.handler.impl.HTTPAuthorizationHandler;
+import io.vertx.ext.auth.impl.Codec;
+import io.vertx.ext.web.handler.HttpException;
+import io.vertx.ext.web.impl.Origin;
+
+public abstract class AbstractOAuth2Handler extends HTTPAuthorizationHandler implements OAuth2AuthHandler, io.vertx.ext.auth.common.ScopedAuthentication> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractOAuth2Handler.class);
+
+ protected final VertxContextPRNG prng;
+ protected final Origin callbackURL;
+ protected final MessageDigest sha256;
+
+ protected final List scopes;
+ protected JsonObject extraParams;
+ protected String prompt;
+ protected int pkce = -1;
+ // explicit signal that tokens are handled as bearer only (meaning, no backend server known)
+ protected boolean bearerOnly = true;
+
+ public AbstractOAuth2Handler(Vertx vertx, OAuth2Auth authProvider, String callbackURL, String realm) {
+ super(authProvider, Type.BEARER, realm);
+
+ // get a reference to the prng
+ this.prng = VertxContextPRNG.current(vertx);
+ // get a reference to the sha-256 digest
+ try {
+ sha256 = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Cannot get instance of SHA-256 MessageDigest", e);
+ }
+ // process callback
+ if (callbackURL != null) {
+ this.callbackURL = Origin.parse(callbackURL);
+ } else {
+ this.callbackURL = null;
+ }
+ // scopes are empty by default
+ this.scopes = Collections.emptyList();
+ }
+
+ protected AbstractOAuth2Handler(AbstractOAuth2Handler> base, List scopes) {
+ super(base.authProvider, Type.BEARER, base.realm);
+ this.prng = base.prng;
+ this.callbackURL = base.callbackURL;
+ this.prompt = base.prompt;
+ this.pkce = base.pkce;
+ this.bearerOnly = base.bearerOnly;
+
+ // get a new reference to the sha-256 digest
+ try {
+ sha256 = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Cannot get instance of SHA-256 MessageDigest", e);
+ }
+ // state copy
+ if (base.extraParams != null) {
+ extraParams = base.extraParams.copy();
+ }
+
+ // apply the new scopes
+ Objects.requireNonNull(scopes, "scopes cannot be null");
+ this.scopes = scopes;
+ }
+
+ @Override
+ public Future authenticate(C context) {
+ // when the handler is working as bearer only, then the `Authorization` header is required
+ return parseAuthorization(context, !bearerOnly)
+ .compose(token -> {
+ // Authorization header can be null when in not in bearerOnly mode
+ if (token == null) {
+ // redirect request to the oauth2 server as we know nothing about this request
+ if (bearerOnly) {
+ // it's a failure both cases but the cause is not the same
+ return Future.failedFuture("callback route is not configured.");
+ }
+ // when this handle is mounted as a catch all, the callback route must be configured before,
+ // as it would shade the callback route. When a request matches the callback path and has the
+ // method GET the exceptional case should not redirect to the oauth2 server as it would become
+ // an infinite redirect loop. In this case an exception must be raised.
+ if (context.request().method() == HttpMethod.GET && context.normalizedPath().equals(callbackURL.resource())) {
+ LOG.warn("The callback route is shaded by the OAuth2AuthHandler, ensure the callback route is added BEFORE the OAuth2AuthHandler route!");
+ return Future.failedFuture(new HttpException(500, "Infinite redirect loop [oauth2 callback]"));
+ } else {
+ if (context.request().method() != HttpMethod.GET) {
+ // we can only redirect GET requests
+ LOG.error("OAuth2 redirect attempt to non GET resource");
+ return Future.failedFuture(new HttpException(405, new IllegalStateException("OAuth2 redirect attempt to non GET resource")));
+ }
+
+ // the redirect is processed as a failure to abort the chain
+ String redirectUri = context.request().uri();
+ try {
+ return Future.failedFuture(new HttpException(302, authURI(context, redirectUri)));
+ } catch (IllegalStateException e) {
+ return Future.failedFuture(e);
+ }
+ }
+ } else {
+ // continue
+ final List scopes = getScopesOrSearchMetadata(this.scopes, context);
+
+ final Credentials credentials =
+ scopes.size() > 0 ? new TokenCredentials(token).setScopes(scopes) : new TokenCredentials(token);
+
+ final SecurityAudit audit = ((AuthenticationContextInternal) context).securityAudit();
+ audit.credentials(credentials);
+
+ return authProvider.authenticate(credentials)
+ .andThen(op -> audit.audit(Marker.AUTHENTICATION, op.succeeded()))
+ .recover(err -> Future.failedFuture(new HttpException(401, err)));
+ }
+ });
+ }
+
+ private String authURI(C context, String redirectURL) {
+
+ String state = null;
+ String codeVerifier = null;
+ String loginHint = null;
+
+ final Session session = context.session();
+
+ if (session == null) {
+ if (pkce > 0) {
+ // we can only handle PKCE with a session
+ throw new IllegalStateException("OAuth2 PKCE requires a session to be present");
+ }
+ } else {
+ // there's a session we can make this request comply to the Oauth2 spec and add an opaque state
+
+ loginHint = session.get("login_hint");
+ // hint will be considered at least once
+ session.remove("login_hint");
+
+ session
+ .put("redirect_uri", redirectURL);
+
+ // create a state value to mitigate replay attacks
+ state = prng.nextString(6);
+ // store the state in the session
+ session
+ .put("state", state);
+
+ if (pkce > 0) {
+ codeVerifier = prng.nextString(pkce);
+ // store the code verifier in the session
+ session
+ .put("pkce", codeVerifier);
+ }
+ }
+
+ final OAuth2AuthorizationURL config = new OAuth2AuthorizationURL();
+
+ if (extraParams != null) {
+ for (Map.Entry entry : extraParams) {
+ if (entry.getValue() != null) {
+ config.putAdditionalParameter(entry.getKey(), entry.getValue().toString());
+ }
+ }
+ }
+
+ config
+ .setState(state != null ? state : redirectURL)
+ .setLoginHint(loginHint)
+ .setPrompt(prompt);
+
+ if (callbackURL != null) {
+ config.setRedirectUri(callbackURL.href());
+ }
+
+ final List scopes = getScopesOrSearchMetadata(this.scopes, context);
+
+ if (scopes.size() > 0) {
+ config.setScopes(scopes);
+ }
+
+ if (codeVerifier != null) {
+ synchronized (sha256) {
+ sha256.update(codeVerifier.getBytes(StandardCharsets.US_ASCII));
+ config
+ .setCodeChallenge(Codec.base64UrlEncode(sha256.digest()))
+ .setCodeChallengeMethod("S256");
+ }
+ }
+
+ return authProvider.authorizeURL(new OAuth2AuthorizationURL(config));
+ }
+
+ @Override
+ public OAuth2AuthHandler extraParams(JsonObject extraParams) {
+ this.extraParams = extraParams;
+ return this;
+ }
+
+ @Override
+ public OAuth2AuthHandler prompt(String prompt) {
+ this.prompt = prompt;
+ return this;
+ }
+
+ @Override
+ public OAuth2AuthHandler pkceVerifierLength(int length) {
+ if (length >= 0) {
+ // requires verification
+ if (length < 43 || length > 128) {
+ throw new IllegalArgumentException("Length must be between 34 and 128");
+ }
+ }
+ this.pkce = length;
+ return this;
+ }
+
+
+
+ private static final Set OPENID_SCOPES = new HashSet<>();
+
+ static {
+ OPENID_SCOPES.add("openid");
+ OPENID_SCOPES.add("profile");
+ OPENID_SCOPES.add("email");
+ OPENID_SCOPES.add("phone");
+ OPENID_SCOPES.add("offline");
+ }
+
+ /**
+ * The default behavior for post-authentication
+ */
+ @Override
+ public void postAuthentication(C ctx, User authenticatedUser) {
+ // the user is authenticated, however the user may not have all the required scopes
+ final List scopes = getScopesOrSearchMetadata(this.scopes, ctx);
+
+ if (scopes.size() > 0) {
+ final User user = ctx.user().get();
+ if (user == null) {
+ // bad state
+ fail(ctx, 403, "no user in the context");
+ return;
+ }
+
+ if (user.principal().containsKey("scope")) {
+ final String userScopes = user.principal().getString("scope");
+ if (userScopes != null) {
+ // user principal contains scope, a basic assertion is required to ensure that
+ // the scopes present match the required ones
+
+ // check if openid is active
+ final boolean openId = userScopes.contains("openid");
+
+ for (String scope : scopes) {
+ // do not assert openid scopes if openid is active
+ if (openId && OPENID_SCOPES.contains(scope)) {
+ continue;
+ }
+
+ int idx = userScopes.indexOf(scope);
+ if (idx != -1) {
+ // match, but is it valid?
+ if (
+ (idx != 0 && userScopes.charAt(idx -1) != ' ') ||
+ (idx + scope.length() != userScopes.length() && userScopes.charAt(idx + scope.length()) != ' ')) {
+ // invalid scope assignment
+ fail(ctx, 403, "principal scope != handler scopes");
+ return;
+ }
+ } else {
+ // invalid scope assignment
+ fail(ctx, 403, "principal scope != handler scopes");
+ return;
+ }
+ }
+ }
+ }
+ }
+ ctx.onContinue();
+ }
+
+ @Override
+ public boolean performsRedirect() {
+ // depending on the time this method is invoked
+ // we can deduct with more accuracy if a redirect is possible or not
+ if (!bearerOnly) {
+ // we know that a redirect is definitely possible
+ // as the callback handler has been created
+ return true;
+ } else {
+ // the callback hasn't been mounted so we need to assume
+ // that if no callbackURL is provided, then there isn't
+ // a redirect happening in this application
+ return callbackURL != null;
+ }
+ }
+
+ abstract protected void fail(C ctx, int code, String msg);
+}
diff --git a/vertx-web-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2AuthHandler.java b/vertx-web-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2AuthHandler.java
new file mode 100644
index 0000000000..f55127e84a
--- /dev/null
+++ b/vertx-web-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2AuthHandler.java
@@ -0,0 +1,80 @@
+package io.vertx.ext.auth.oauth2;
+
+import java.util.List;
+
+import io.vertx.codegen.annotations.Fluent;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.AuthenticationHandler;
+
+public interface OAuth2AuthHandler extends AuthenticationHandler {
+
+ /**
+ * Extra parameters needed to be passed while requesting a token.
+ *
+ * @param extraParams extra optional parameters.
+ * @return self
+ */
+ @Fluent
+ OAuth2AuthHandler extraParams(JsonObject extraParams);
+
+ /**
+ * Return a new instance with the internal state copied from the caller but the scopes to be requested during
+ * a token request are unique to the instance. When scopes are applied to the handler, the default scopes from the
+ * route metadata will be ignored.
+ *
+ * @param scope scope.
+ * @return new instance of this interface.
+ */
+ @Fluent
+ OAuth2AuthHandler withScope(String scope);
+
+ /**
+ * Return a new instance with the internal state copied from the caller but the scopes to be requested during
+ * a token request are unique to the instance. When scopes are applied to the handler, the default scopes from the
+ * route metadata will be ignored.
+ *
+ * @param scopes scopes.
+ * @return new instance of this interface.
+ */
+ @Fluent
+ OAuth2AuthHandler withScopes(List scopes);
+
+ /**
+ * Indicates the type of user interaction that is required. Not all providers support this or the full list.
+ *
+ * Well known values are:
+ *
+ *
+ * - login will force the user to enter their credentials on that request, negating single-sign on.
+ * - none is the opposite - it will ensure that the user isn't presented with any interactive prompt
+ * whatsoever. If the request can't be completed silently via single-sign on, the Microsoft identity platform
+ * endpoint will return an interaction_required error.
+ * - consent will trigger the OAuth consent dialog after the user signs in, asking the user to grant
+ * permissions to the app.
+ * - select_account will interrupt single sign-on providing account selection experience listing all the
+ * accounts either in session or any remembered account or an option to choose to use a different account
+ * altogether.
+ *
+ *
+ *
+ * @param prompt the prompt choice.
+ * @return self
+ */
+ @Fluent
+ OAuth2AuthHandler prompt(String prompt);
+
+ /**
+ * PKCE (RFC 7636) is an extension to the Authorization Code flow to prevent several attacks and to be able to
+ * securely perform the OAuth exchange from public clients.
+ *
+ * It was originally designed to protect mobile apps, but its ability to prevent authorization code injection
+ * makes it useful for every OAuth client, even web apps that use a client secret.
+ *
+ * @param length A number between 43 and 128. Or -1 to disable.
+ * @return self
+ */
+ @Fluent
+ OAuth2AuthHandler pkceVerifierLength(int length);
+
+}
diff --git a/vertx-web-graphql/src/main/java/examples/GraphQLExamples.java b/vertx-web-graphql/src/main/java/examples/GraphQLExamples.java
index e01b7ee269..6414208b1e 100644
--- a/vertx-web-graphql/src/main/java/examples/GraphQLExamples.java
+++ b/vertx-web-graphql/src/main/java/examples/GraphQLExamples.java
@@ -29,7 +29,7 @@
import io.vertx.ext.web.FileUpload;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.UserContext;
+import io.vertx.ext.auth.common.UserContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.graphql.*;
import io.vertx.ext.web.handler.graphql.instrumentation.JsonObjectAdapter;
diff --git a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/RouterBuilder.java b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/RouterBuilder.java
index 4f5ca8a36c..5565e1fd5c 100644
--- a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/RouterBuilder.java
+++ b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/RouterBuilder.java
@@ -19,7 +19,6 @@
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.handler.AuthenticationHandler;
import io.vertx.openapi.contract.OpenAPIContract;
import io.vertx.ext.web.openapi.router.impl.RouterBuilderImpl;
import io.vertx.openapi.validation.RequestUtils;
diff --git a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/AuthenticationHandlers.java b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/AuthenticationHandlers.java
index 69ba9d71ab..2a69286343 100644
--- a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/AuthenticationHandlers.java
+++ b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/AuthenticationHandlers.java
@@ -3,7 +3,8 @@
import io.vertx.core.Future;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
-import io.vertx.ext.web.handler.AuthenticationHandler;
+import io.vertx.ext.auth.common.AuthenticationHandler;
+import io.vertx.ext.web.handler.WebAuthenticationHandler;
import io.vertx.ext.web.handler.ChainAuthHandler;
import io.vertx.ext.web.handler.OAuth2AuthHandler;
import io.vertx.ext.web.handler.SimpleAuthenticationHandler;
@@ -20,12 +21,12 @@
*/
class AuthenticationHandlers {
- private static final AuthenticationHandler ANONYMOUS_SUCCESS_AUTH_HANDLER =
+ private static final WebAuthenticationHandler ANONYMOUS_SUCCESS_AUTH_HANDLER =
SimpleAuthenticationHandler
.create()
.authenticate(ctx -> Future.succeededFuture());
- private final Map> securityHandlers;
+ private final Map> securityHandlers;
private final Map callbackHandlers;
AuthenticationHandlers() {
@@ -33,7 +34,7 @@ class AuthenticationHandlers {
this.callbackHandlers = new HashMap<>();
}
- protected void addRequirement(String name, AuthenticationHandler handler, String callback) {
+ protected void addRequirement(String name, WebAuthenticationHandler handler, String callback) {
securityHandlers
.computeIfAbsent(name, k -> new ArrayList<>())
.add(handler);
@@ -53,16 +54,16 @@ protected void addRequirement(String name, AuthenticationHandler handler, String
* The input array is an OR of different AND security requirements
*/
protected void solve(Operation operation, Route route, boolean failOnNotFound) {
- AuthenticationHandler authn = or(route, operation.getSecurityRequirements(), failOnNotFound);
+ WebAuthenticationHandler authn = or(route, operation.getSecurityRequirements(), failOnNotFound);
if (authn != null) {
route.handler(authn);
}
}
- private List resolveHandlers(Route route, String name, List scopes,
+ private List resolveHandlers(Route route, String name, List scopes,
boolean failOnNotFound) {
- List authenticationHandlers;
+ List authenticationHandlers;
if (failOnNotFound) {
authenticationHandlers = Optional
.ofNullable(this.securityHandlers.get(name))
@@ -81,8 +82,10 @@ private List resolveHandlers(Route route, String name, Li
authenticationHandlers = authenticationHandlers
.stream()
.map(authHandler -> {
- if (authHandler instanceof ScopedAuthentication>) {
- return ((ScopedAuthentication>) authHandler).withScopes(scopes);
+ if (authHandler instanceof ScopedAuthentication) {
+ AuthenticationHandler> scopedHandler = ((ScopedAuthentication>) authHandler).withScopes(scopes);
+ WebAuthenticationHandler webAuthHandler = (WebAuthenticationHandler)scopedHandler;
+ return webAuthHandler;
} else {
return authHandler;
}
@@ -93,8 +96,8 @@ private List resolveHandlers(Route route, String name, Li
return authenticationHandlers;
}
- private AuthenticationHandler and(Route route, SecurityRequirement securityRequirement, boolean failOnNotFound) {
- List handlers = securityRequirement.getNames()
+ private WebAuthenticationHandler and(Route route, SecurityRequirement securityRequirement, boolean failOnNotFound) {
+ List handlers = securityRequirement.getNames()
.stream()
.flatMap(name -> resolveHandlers(route, name, securityRequirement.getScopes(name), failOnNotFound).stream())
.collect(Collectors.toList());
@@ -113,7 +116,7 @@ private AuthenticationHandler and(Route route, SecurityRequirement securityRequi
return authHandler;
}
- private AuthenticationHandler or(Route route, List securityRequirements,
+ private WebAuthenticationHandler or(Route route, List securityRequirements,
boolean failOnNotFound) {
if (securityRequirements == null || securityRequirements.isEmpty()) {
return null;
diff --git a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderImpl.java b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderImpl.java
index e892ba33d8..37a27a8815 100644
--- a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderImpl.java
+++ b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderImpl.java
@@ -20,7 +20,7 @@
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.handler.AuthenticationHandler;
+import io.vertx.ext.web.handler.WebAuthenticationHandler;
import io.vertx.ext.web.handler.InputTrustHandler;
import io.vertx.ext.web.openapi.router.OpenAPIRoute;
import io.vertx.ext.web.openapi.router.RequestExtractor;
@@ -88,7 +88,7 @@ public RouterBuilder rootHandler(Handler rootHandler) {
}
@Override
- public RouterBuilder security(String securitySchemeName, AuthenticationHandler authenticationHandler,
+ public RouterBuilder security(String securitySchemeName, WebAuthenticationHandler authenticationHandler,
String callback) {
securityHandlers.addRequirement(securitySchemeName, authenticationHandler, callback);
return this;
diff --git a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderInternal.java b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderInternal.java
index 43e76602f5..dd943fa46c 100644
--- a/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderInternal.java
+++ b/vertx-web-openapi-router/src/main/java/io/vertx/ext/web/openapi/router/impl/RouterBuilderInternal.java
@@ -16,7 +16,7 @@
package io.vertx.ext.web.openapi.router.impl;
import io.vertx.codegen.annotations.Fluent;
-import io.vertx.ext.web.handler.AuthenticationHandler;
+import io.vertx.ext.web.handler.WebAuthenticationHandler;
import io.vertx.ext.web.openapi.router.RouterBuilder;
interface RouterBuilderInternal extends RouterBuilder {
@@ -33,5 +33,5 @@ interface RouterBuilderInternal extends RouterBuilder {
* @return self
*/
@Fluent
- RouterBuilder security(String securitySchemeName, AuthenticationHandler handler, String callback);
+ RouterBuilder security(String securitySchemeName, WebAuthenticationHandler handler, String callback);
}
diff --git a/vertx-web/pom.xml b/vertx-web/pom.xml
index 263fb04b97..78ab5c2a6a 100644
--- a/vertx-web/pom.xml
+++ b/vertx-web/pom.xml
@@ -39,6 +39,12 @@
io.vertx
vertx-auth-common
+
+
+ io.vertx
+ vertx-web-auth-common
+ ${project.version}
+
io.vertx
vertx-bridge-common
@@ -51,11 +57,24 @@
vertx-auth-jwt
true
+
+
+ io.vertx
+ vertx-web-auth-jwt
+ ${project.version}
+ true
+
io.vertx
vertx-auth-oauth2
true
+
+
+ io.vertx
+ vertx-web-auth-oauth2
+ ${project.version}
+
io.vertx
vertx-auth-htdigest
@@ -109,6 +128,11 @@
jackson-databind
test
+
+ io.vertx
+ vertx-auth-jwt-grpc
+ 5.0.0-SNAPSHOT
+
diff --git a/vertx-web/src/main/asciidoc/index.adoc b/vertx-web/src/main/asciidoc/index.adoc
index e5c07c626a..c3309ac768 100644
--- a/vertx-web/src/main/asciidoc/index.adoc
+++ b/vertx-web/src/main/asciidoc/index.adoc
@@ -1199,7 +1199,7 @@ make sure your authentication handler is before your application handlers on tho
----
If the authentication handler has successfully authenticated the user it will inject a {@link io.vertx.ext.auth.User}
-object into the {@link io.vertx.ext.web.UserContext} so it's available in your handlers from the routing context:
+object into the {@link io.vertx.ext.auth.common.UserContext} so it's available in your handlers from the routing context:
{@link io.vertx.ext.web.RoutingContext#user()}.
If you want your User object to be stored in the session so it's available between requests so you don't have to
@@ -1207,7 +1207,7 @@ authenticate on each request, then you should make sure you have a session handl
Once you have your user object you can also programmatically use the methods on it to authorize the user.
-If you want to cause the user to be logged out you can call {@link io.vertx.ext.web.UserContext#logout()}
+If you want to cause the user to be logged out you can call {@link io.vertx.ext.auth.common.UserContext#logout()}
on the routing context `user` getter. The logout will remove the user from the session if there is one and perform a redirect to an optional uri or `/` by default.
=== HTTP Basic Authentication
diff --git a/vertx-web/src/main/java/examples/WebExamples.java b/vertx-web/src/main/java/examples/WebExamples.java
index 9fc55f9488..f8ab1f216d 100644
--- a/vertx-web/src/main/java/examples/WebExamples.java
+++ b/vertx-web/src/main/java/examples/WebExamples.java
@@ -808,14 +808,14 @@ public void example37(Vertx vertx, AuthenticationProvider authProvider, Router r
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
- AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
+ WebAuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
}
public void example38(Vertx vertx, AuthenticationProvider authProvider, Router router) {
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
- AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
+ WebAuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
// All requests to paths starting with '/private/' will be protected
router.route("/private/*").handler(basicAuthHandler);
@@ -1102,7 +1102,7 @@ public void example48(Vertx vertx, AuthenticationProvider authProvider) {
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
- AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
+ WebAuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
router.route("/eventbus/*").handler(basicAuthHandler);
@@ -1813,7 +1813,7 @@ public void example77(Vertx vertx, Router router) {
router.allowForward(AllowForwardHeaders.NONE);
}
- public void example78(Router router, AuthenticationHandler authNHandlerA, AuthenticationHandler authNHandlerB, AuthenticationHandler authNHandlerC) {
+ public void example78(Router router, WebAuthenticationHandler authNHandlerA, WebAuthenticationHandler authNHandlerB, WebAuthenticationHandler authNHandlerC) {
// Chain will verify (A Or (B And C))
ChainAuthHandler chain =
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java b/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java
index f029f2fc76..d860ac2fa7 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java
@@ -24,6 +24,8 @@
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.json.EncodeException;
import io.vertx.core.json.Json;
+import io.vertx.ext.auth.common.AuthenticationContext;
+import io.vertx.ext.auth.common.UserContext;
import io.vertx.ext.web.impl.ParsableMIMEValue;
import io.vertx.ext.web.impl.Utils;
@@ -55,7 +57,7 @@
* @author Tim Fox
*/
@VertxGen
-public interface RoutingContext {
+public interface RoutingContext extends AuthenticationContext {
/**
* @return the HTTP request object
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/Session.java b/vertx-web/src/main/java/io/vertx/ext/web/Session.java
index 6eecd038c1..498f2265e5 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/Session.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/Session.java
@@ -16,141 +16,12 @@
package io.vertx.ext.web;
-import io.vertx.codegen.annotations.Fluent;
-import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
-import java.util.Map;
-import java.util.function.Function;
-
/**
- * Represents a browser session.
- *
- * Sessions persist between HTTP requests for a single browser session. They are deleted when the browser is closed, or
- * they time-out. Session cookies are used to maintain sessions using a secure UUID.
- *
- * Sessions can be used to maintain data for a browser session, e.g. a shopping basket.
- *
- * The context must have first been routed to a {@link io.vertx.ext.web.handler.SessionHandler}
- * for sessions to be available.
- *
- * @author Tim Fox
+ * @see io.vertx.ext.auth.common.Session
*/
@VertxGen
-public interface Session {
-
- /**
- * @return The new unique ID of the session.
- */
- Session regenerateId();
-
- /**
- * @return The unique ID of the session. This is generated using a random secure UUID.
- */
- String id();
-
- /**
- * Put some data in a session
- *
- * @param key the key for the data
- * @param obj the data
- * @return a reference to this, so the API can be used fluently
- */
- @Fluent
- Session put(String key, Object obj);
-
- /**
- * Put some data in a session if absent
- *
- * @param key the key for the data
- * @param obj the data
- * @return a reference to this, so the API can be used fluently
- */
- @Fluent
- Session putIfAbsent(String key, Object obj);
-
- /**
- * Put some data in a session if absent.
- *
- * If the specified key is not already associated with a value (or is mapped
- * to {@code null}), attempts to compute its value using the given mapping
- * function and enters it into this map unless {@code null}.
- *
- * @param key the key for the data
- * @param mappingFunction a mapping function
- * @return a reference to this, so the API can be used fluently
- */
- @Fluent
- Session computeIfAbsent(String key, Function mappingFunction);
-
- /**
- * Get some data from the session
- *
- * @param key the key of the data
- * @return the data
- */
- T get(String key);
-
- /**
- * Remove some data from the session
- *
- * @param key the key of the data
- * @return the data that was there or null if none there
- */
- T remove(String key);
-
- /**
- * @return the session data as a map
- */
- @GenIgnore(GenIgnore.PERMITTED_TYPE)
- Map data();
-
- /**
- * @return true if the session has data
- */
- boolean isEmpty();
-
- /**
- * @return the time the session was last accessed
- */
- long lastAccessed();
-
- /**
- * Destroy the session
- */
- void destroy();
-
- /**
- * @return has the session been destroyed?
- */
- boolean isDestroyed();
-
- /**
- * @return has the session been renewed?
- */
- boolean isRegenerated();
-
- /**
- * @return old ID if renewed
- */
- String oldId();
-
- /**
- * @return the amount of time in ms, after which the session will expire, if not accessed.
- */
- long timeout();
-
- /**
- * Mark the session as being accessed.
- */
- void setAccessed();
+public interface Session extends io.vertx.ext.auth.common.Session {
- /**
- * The short representation of the session to be added to the session cookie. By default is the session id.
- *
- * @return short representation string.
- */
- default String value() {
- return id();
- }
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/APIKeyHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/APIKeyHandler.java
index f3ddb93411..1c29957383 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/APIKeyHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/APIKeyHandler.java
@@ -33,7 +33,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface APIKeyHandler extends AuthenticationHandler {
+public interface APIKeyHandler extends WebAuthenticationHandler {
/**
* Create an API Key authentication handler
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/BasicAuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/BasicAuthHandler.java
index 93e09d5ab2..44e8ccece5 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/BasicAuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/BasicAuthHandler.java
@@ -26,7 +26,7 @@
* @author Tim Fox
*/
@VertxGen
-public interface BasicAuthHandler extends AuthenticationHandler {
+public interface BasicAuthHandler extends WebAuthenticationHandler {
/**
* The default realm to use
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/ChainAuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/ChainAuthHandler.java
index a9f0c2b017..8422800839 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/ChainAuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/ChainAuthHandler.java
@@ -25,7 +25,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface ChainAuthHandler extends AuthenticationHandler {
+public interface ChainAuthHandler extends WebAuthenticationHandler {
/**
* Create a chain authentication handler that will assert that all handlers pass the verification.
@@ -51,5 +51,5 @@ static ChainAuthHandler any() {
*
*/
@Fluent
- ChainAuthHandler add(AuthenticationHandler other);
+ ChainAuthHandler add(WebAuthenticationHandler other);
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/DigestAuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/DigestAuthHandler.java
index 5a7625e349..8d354f5453 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/DigestAuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/DigestAuthHandler.java
@@ -27,7 +27,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface DigestAuthHandler extends AuthenticationHandler {
+public interface DigestAuthHandler extends WebAuthenticationHandler {
/**
* The default nonce expire timeout to use in milliseconds.
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/FormLoginHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/FormLoginHandler.java
index 1a2b97bb3c..7250368f58 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/FormLoginHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/FormLoginHandler.java
@@ -29,7 +29,7 @@
* @author Tim Fox
*/
@VertxGen
-public interface FormLoginHandler extends AuthenticationHandler {
+public interface FormLoginHandler extends WebAuthenticationHandler {
/**
* The default value of the form attribute which will contain the username
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/JWTAuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/JWTAuthHandler.java
index 70cc1172c5..bb880fe639 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/JWTAuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/JWTAuthHandler.java
@@ -16,26 +16,25 @@
package io.vertx.ext.web.handler;
-import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.ext.auth.jwt.JWTAuth;
+import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.impl.JWTAuthHandlerImpl;
-import java.util.List;
-
/**
* An auth handler that provides JWT Authentication support.
*
* @author Paulo Lopes
*/
@VertxGen
-public interface JWTAuthHandler extends AuthenticationHandler {
+// TODO this will alter the signature of the interface. Originally only the impl did implement ScopedAuthentication - now we also expose it via the interface.
+public interface JWTAuthHandler extends WebAuthenticationHandler, io.vertx.ext.auth.jwt.JWTAuthHandler, io.vertx.ext.web.handler.impl.ScopedAuthentication> {
/**
- * Create a JWT auth handler. When no scopes are explicit declared, the default scopes will be looked up from the
- * route metadata.
+ * Create a JWT auth handler. When no scopes are explicit declared, the default scopes will be looked up from the route metadata.
*
- * @param authProvider the auth provider to use
+ * @param authProvider
+ * the auth provider to use
* @return the auth handler
*/
static JWTAuthHandler create(JWTAuth authProvider) {
@@ -43,42 +42,14 @@ static JWTAuthHandler create(JWTAuth authProvider) {
}
/**
- * Create a JWT auth handler. When no scopes are explicit declared, the default scopes will be looked up from the
- * route metadata.
+ * Create a JWT auth handler. When no scopes are explicit declared, the default scopes will be looked up from the route metadata.
*
- * @param authProvider the auth provider to use
+ * @param authProvider
+ * the auth provider to use
* @return the auth handler
*/
static JWTAuthHandler create(JWTAuth authProvider, String realm) {
return new JWTAuthHandlerImpl(authProvider, realm);
}
- /**
- * Set the scope delimiter. By default this is a space character.
- *
- * @param delimiter scope delimiter.
- * @return fluent self.
- */
- @Fluent
- JWTAuthHandler scopeDelimiter(String delimiter);
-
- /**
- * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token
- * request are unique to the instance. When scopes are applied to the handler, the default scopes from the route
- * metadata will be ignored.
- *
- * @param scope scope.
- * @return new instance of this interface.
- */
- JWTAuthHandler withScope(String scope);
-
- /**
- * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token
- * request are unique to the instance. When scopes are applied to the handler, the default scopes from the route
- * metadata will be ignored.
- *
- * @param scopes scopes.
- * @return new instance of this interface.
- */
- JWTAuthHandler withScopes(List scopes);
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/OAuth2AuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/OAuth2AuthHandler.java
index 2f84394442..f9a8b2ebca 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/OAuth2AuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/OAuth2AuthHandler.java
@@ -19,12 +19,11 @@
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Vertx;
-import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.oauth2.OAuth2Auth;
import io.vertx.ext.web.Route;
+import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.impl.OAuth2AuthHandlerImpl;
-
-import java.util.List;
+import io.vertx.ext.web.impl.OrderListener;
/**
* An auth handler that provides OAuth2 Authentication support. This handler is suitable for AuthCode flows.
@@ -32,7 +31,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface OAuth2AuthHandler extends AuthenticationHandler {
+public interface OAuth2AuthHandler extends WebAuthenticationHandler, io.vertx.ext.auth.oauth2.OAuth2AuthHandler, io.vertx.ext.web.handler.impl.ScopedAuthentication>, OrderListener {
/**
* Create a OAuth2 auth handler with host pinning. When no scopes are explicit declared, the default scopes will be
@@ -66,74 +65,6 @@ static OAuth2AuthHandler create(Vertx vertx, OAuth2Auth authProvider) {
return new OAuth2AuthHandlerImpl(vertx, authProvider, null);
}
- /**
- * Extra parameters needed to be passed while requesting a token.
- *
- * @param extraParams extra optional parameters.
- * @return self
- */
- @Fluent
- OAuth2AuthHandler extraParams(JsonObject extraParams);
-
- /**
- * Return a new instance with the internal state copied from the caller but the scopes to be requested during
- * a token request are unique to the instance. When scopes are applied to the handler, the default scopes from the
- * route metadata will be ignored.
- *
- * @param scope scope.
- * @return new instance of this interface.
- */
- @Fluent
- OAuth2AuthHandler withScope(String scope);
-
- /**
- * Return a new instance with the internal state copied from the caller but the scopes to be requested during
- * a token request are unique to the instance. When scopes are applied to the handler, the default scopes from the
- * route metadata will be ignored.
- *
- * @param scopes scopes.
- * @return new instance of this interface.
- */
- @Fluent
- OAuth2AuthHandler withScopes(List scopes);
-
- /**
- * Indicates the type of user interaction that is required. Not all providers support this or the full list.
- *
- * Well known values are:
- *
- *
- * - login will force the user to enter their credentials on that request, negating single-sign on.
- * - none is the opposite - it will ensure that the user isn't presented with any interactive prompt
- * whatsoever. If the request can't be completed silently via single-sign on, the Microsoft identity platform
- * endpoint will return an interaction_required error.
- * - consent will trigger the OAuth consent dialog after the user signs in, asking the user to grant
- * permissions to the app.
- * - select_account will interrupt single sign-on providing account selection experience listing all the
- * accounts either in session or any remembered account or an option to choose to use a different account
- * altogether.
- *
- *
- *
- * @param prompt the prompt choice.
- * @return self
- */
- @Fluent
- OAuth2AuthHandler prompt(String prompt);
-
- /**
- * PKCE (RFC 7636) is an extension to the Authorization Code flow to prevent several attacks and to be able to
- * securely perform the OAuth exchange from public clients.
- *
- * It was originally designed to protect mobile apps, but its ability to prevent authorization code injection
- * makes it useful for every OAuth client, even web apps that use a client secret.
- *
- * @param length A number between 43 and 128. Or -1 to disable.
- * @return self
- */
- @Fluent
- OAuth2AuthHandler pkceVerifierLength(int length);
-
/**
* add the callback handler to a given route.
* @param route a given route e.g.: {@code /callback}
@@ -141,4 +72,5 @@ static OAuth2AuthHandler create(Vertx vertx, OAuth2Auth authProvider) {
*/
@Fluent
OAuth2AuthHandler setupCallback(Route route);
+
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/OtpAuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/OtpAuthHandler.java
index ac0c4f4753..8f8917ddfa 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/OtpAuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/OtpAuthHandler.java
@@ -22,7 +22,6 @@
import io.vertx.ext.auth.otp.totp.TotpAuth;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.UserContext;
import io.vertx.ext.web.handler.impl.HotpAuthHandlerImpl;
import io.vertx.ext.web.handler.impl.TotpAuthHandlerImpl;
@@ -32,7 +31,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface OtpAuthHandler extends AuthenticationHandler {
+public interface OtpAuthHandler extends WebAuthenticationHandler {
/**
* Create a new instance of this handler using a time based one time password authentication provider.
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/RedirectAuthHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/RedirectAuthHandler.java
index 9b2b4f0278..f92192a6b4 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/RedirectAuthHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/RedirectAuthHandler.java
@@ -26,7 +26,7 @@
* @author Tim Fox
*/
@VertxGen
-public interface RedirectAuthHandler extends AuthenticationHandler {
+public interface RedirectAuthHandler extends WebAuthenticationHandler {
/**
* Default path the user will be redirected to
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/SimpleAuthenticationHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/SimpleAuthenticationHandler.java
index 32b5b31312..10c57a915e 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/SimpleAuthenticationHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/SimpleAuthenticationHandler.java
@@ -37,7 +37,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface SimpleAuthenticationHandler extends AuthenticationHandler {
+public interface SimpleAuthenticationHandler extends WebAuthenticationHandler {
/**
* Creates a new instance of the simple authentication handler.
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthenticationHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthenticationHandler.java
new file mode 100644
index 0000000000..bc392465e9
--- /dev/null
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthenticationHandler.java
@@ -0,0 +1,20 @@
+package io.vertx.ext.web.handler;
+
+import io.vertx.codegen.annotations.VertxGen;
+import io.vertx.ext.auth.common.AuthenticationHandler;
+import io.vertx.ext.web.RoutingContext;
+
+/**
+ * Base interface for Vert.x Web authentication handlers.
+ *
+ * An auth handler allows your application to provide authentication support.
+ *
+ * An Auth handler may require a {@link SessionHandler} to be on the routing chain before it.
+ *
+ * @author Tim Fox
+ * @author Paulo Lopes
+ */
+@VertxGen(concrete = false)
+public interface WebAuthenticationHandler extends AuthenticationHandler {
+
+}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthnHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthnHandler.java
index 82b869399c..f8dc090a1d 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthnHandler.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/WebAuthnHandler.java
@@ -27,7 +27,7 @@
* @author Paulo Lopes
*/
@VertxGen
-public interface WebAuthnHandler extends AuthenticationHandler {
+public interface WebAuthnHandler extends WebAuthenticationHandler {
/**
* Create a WebAuthN auth handler. This handler expects at least the response callback to be installed.
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/APIKeyHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/APIKeyHandlerImpl.java
index 65cb6d4147..e408b1370f 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/APIKeyHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/APIKeyHandlerImpl.java
@@ -30,10 +30,12 @@
import java.util.function.Function;
+import static io.vertx.ext.web.handler.HttpException.UNAUTHORIZED;
+
/**
* @author Paulo Lopes
*/
-public class APIKeyHandlerImpl extends AuthenticationHandlerImpl implements APIKeyHandler {
+public class APIKeyHandlerImpl extends WebAuthenticationHandlerImpl implements APIKeyHandler {
enum Type {
HEADER,
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/BasicAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/BasicAuthHandlerImpl.java
index 0aa184899e..894806d9b1 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/BasicAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/BasicAuthHandlerImpl.java
@@ -35,7 +35,7 @@
* @author Paulo Lopes
* @author Tim Fox
*/
-public class BasicAuthHandlerImpl extends HTTPAuthorizationHandler implements BasicAuthHandler {
+public class BasicAuthHandlerImpl extends WebHTTPAuthorizationHandler implements BasicAuthHandler {
public BasicAuthHandlerImpl(AuthenticationProvider authProvider, String realm) {
super(authProvider, Type.BASIC, realm);
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ChainAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ChainAuthHandlerImpl.java
index bb85a93303..db49bfb04d 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ChainAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ChainAuthHandlerImpl.java
@@ -8,17 +8,19 @@
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
+import io.vertx.ext.auth.common.handler.AuthenticationHandlerInternal;
import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.handler.*;
import java.util.ArrayList;
import java.util.List;
-public class ChainAuthHandlerImpl extends AuthenticationHandlerImpl implements ChainAuthHandler {
+public class ChainAuthHandlerImpl extends WebAuthenticationHandlerImpl implements ChainAuthHandler {
private static final Logger LOG = LoggerFactory.getLogger(ChainAuthHandler.class);
- private final List handlers = new ArrayList<>();
+ private final List> handlers = new ArrayList<>();
private final boolean all;
private int willRedirect = -1;
@@ -34,11 +36,11 @@ public boolean performsRedirect() {
}
@Override
- public synchronized ChainAuthHandler add(AuthenticationHandler other) {
+ public synchronized ChainAuthHandler add(WebAuthenticationHandler other) {
if (performsRedirect()) {
throw new IllegalStateException("Cannot add a handler after a handler known to perform a HTTP redirect: " + handlers.get(willRedirect));
}
- final AuthenticationHandlerInternal otherInternal = (AuthenticationHandlerInternal) other;
+ final AuthenticationHandlerInternal otherInternal = (AuthenticationHandlerInternal) other;
// control if we should not allow more handlers due to the possibility of a redirect to happen
if (otherInternal.performsRedirect()) {
willRedirect = handlers.size();
@@ -77,7 +79,7 @@ private void iterate(final int idx, final RoutingContext ctx, User result, Throw
}
// parse the request in order to extract the credentials object
- final AuthenticationHandlerInternal authHandler = handlers.get(idx);
+ final AuthenticationHandlerInternal authHandler = handlers.get(idx);
authHandler
.authenticate(ctx)
@@ -120,7 +122,7 @@ private void iterate(final int idx, final RoutingContext ctx, User result, Throw
@Override
public boolean setAuthenticateHeader(RoutingContext ctx) {
boolean added = false;
- for (AuthenticationHandlerInternal authHandler : handlers) {
+ for (AuthenticationHandlerInternal authHandler : handlers) {
if (all && added) {
// we can only allow 1 header in this case,
// otherwise we tell the user agent to pick the strongest,
@@ -132,4 +134,5 @@ public boolean setAuthenticateHeader(RoutingContext ctx) {
}
return added;
}
+
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/DigestAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/DigestAuthHandlerImpl.java
index 8dc9ee3e70..b743763a20 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/DigestAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/DigestAuthHandlerImpl.java
@@ -26,6 +26,7 @@
import io.vertx.ext.auth.VertxContextPRNG;
import io.vertx.ext.auth.audit.Marker;
import io.vertx.ext.auth.audit.SecurityAudit;
+import io.vertx.ext.auth.common.handler.impl.HTTPAuthorizationHandler;
import io.vertx.ext.auth.htdigest.HtdigestAuth;
import io.vertx.ext.auth.htdigest.HtdigestCredentials;
import io.vertx.ext.web.RoutingContext;
@@ -42,11 +43,12 @@
import java.util.regex.Pattern;
import static io.vertx.ext.auth.impl.Codec.base16Encode;
+import static io.vertx.ext.web.handler.HttpException.UNAUTHORIZED;
/**
* @author Paulo Lopes
*/
-public class DigestAuthHandlerImpl extends HTTPAuthorizationHandler implements DigestAuthHandler {
+public class DigestAuthHandlerImpl extends WebHTTPAuthorizationHandler implements DigestAuthHandler {
private final static Logger LOG = LoggerFactory.getLogger(HTTPAuthorizationHandler.class);
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/FormLoginHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/FormLoginHandlerImpl.java
index 428df66c1c..48d7f15fee 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/FormLoginHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/FormLoginHandlerImpl.java
@@ -32,10 +32,13 @@
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.impl.RoutingContextInternal;
+import static io.vertx.ext.web.handler.HttpException.BAD_METHOD;
+import static io.vertx.ext.web.handler.HttpException.BAD_REQUEST;
+
/**
* @author Tim Fox
*/
-public class FormLoginHandlerImpl extends AuthenticationHandlerImpl implements FormLoginHandler {
+public class FormLoginHandlerImpl extends WebAuthenticationHandlerImpl implements FormLoginHandler {
private String usernameParam;
private String passwordParam;
@@ -104,7 +107,7 @@ public Future authenticate(RoutingContext context) {
}
@Override
- public void postAuthentication(RoutingContext ctx) {
+ public void postAuthentication(RoutingContext ctx, User user) {
HttpServerRequest req = ctx.request();
Session session = ctx.session();
if (session != null) {
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HotpAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HotpAuthHandlerImpl.java
index 215c96ea09..e40c3915cb 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HotpAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/HotpAuthHandlerImpl.java
@@ -36,7 +36,7 @@
/**
* @author Paulo Lopes
*/
-public class HotpAuthHandlerImpl extends AuthenticationHandlerImpl implements OtpAuthHandler, OrderListener {
+public class HotpAuthHandlerImpl extends WebAuthenticationHandlerImpl implements OtpAuthHandler, OrderListener {
private final OtpKeyGenerator otpKeyGen;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/JWTAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/JWTAuthHandlerImpl.java
index 5550c85015..67c9318a8a 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/JWTAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/JWTAuthHandlerImpl.java
@@ -16,78 +16,29 @@
package io.vertx.ext.web.handler.impl;
-import io.vertx.core.Future;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.auth.User;
-import io.vertx.ext.auth.audit.Marker;
-import io.vertx.ext.auth.audit.SecurityAudit;
-import io.vertx.ext.auth.authentication.TokenCredentials;
+import java.util.ArrayList;
+
+import java.util.List;
+import java.util.Objects;
+
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.auth.jwt.AbstractJWTHandler;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.handler.JWTAuthHandler;
-import io.vertx.ext.web.impl.RoutingContextInternal;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* @author Paulo Lopes
*/
-public class JWTAuthHandlerImpl extends HTTPAuthorizationHandler implements JWTAuthHandler, ScopedAuthentication {
-
- private final List scopes;
- private String delimiter;
+public class JWTAuthHandlerImpl extends AbstractJWTHandler implements JWTAuthHandler {
public JWTAuthHandlerImpl(JWTAuth authProvider, String realm) {
super(authProvider, Type.BEARER, realm);
- scopes = Collections.emptyList();
- this.delimiter = " ";
}
private JWTAuthHandlerImpl(JWTAuthHandlerImpl base, List scopes, String delimiter) {
- super(base.authProvider, Type.BEARER, base.realm);
- Objects.requireNonNull(scopes, "scopes cannot be null");
- this.scopes = scopes;
- Objects.requireNonNull(delimiter, "delimiter cannot be null");
- this.delimiter = delimiter;
- }
-
- @Override
- public Future authenticate(RoutingContext context) {
-
- return parseAuthorization(context)
- .compose(token -> {
- int segments = 0;
- for (int i = 0; i < token.length(); i++) {
- char c = token.charAt(i);
- if (c == '.') {
- if (++segments == 3) {
- return Future.failedFuture(new HttpException(400, "Too many segments in token"));
- }
- continue;
- }
- if (Character.isLetterOrDigit(c) || c == '-' || c == '_') {
- continue;
- }
- // invalid character
- return Future.failedFuture(new HttpException(400, "Invalid character in token: " + (int) c));
- }
-
- final TokenCredentials credentials = new TokenCredentials(token);
- final SecurityAudit audit = ((RoutingContextInternal) context).securityAudit();
- audit.credentials(credentials);
-
- return
- authProvider
- .authenticate(new TokenCredentials(token))
- .andThen(op -> audit.audit(Marker.AUTHENTICATION, op.succeeded()))
- .recover(err -> Future.failedFuture(new HttpException(401, err)));
- });
+ super(base.authProvider, scopes, delimiter, base.realm);
}
@Override
@@ -111,51 +62,41 @@ public JWTAuthHandler scopeDelimiter(String delimiter) {
return this;
}
+ // TODO remove duplicated code from WebAuthenticationHandlerImpl
/**
- * The default behavior for post-authentication
+ * This method is protected so custom auth handlers can override the default error handling
*/
- @Override
- public void postAuthentication(RoutingContext ctx) {
- final User user = ctx.user().get();
- if (user == null) {
- // bad state
- ctx.fail(403, new IllegalStateException("no user in the context"));
- return;
- }
- // the user is authenticated, however the user may not have all the required scopes
- final List scopes = getScopesOrSearchMetadata(this.scopes, ctx);
-
- if (scopes.size() > 0) {
- final JsonObject jwt = user.get("accessToken");
- if (jwt == null) {
- ctx.fail(403, new IllegalStateException("Invalid JWT: null"));
- return;
- }
-
- if (jwt.getValue("scope") == null) {
- ctx.fail(403, new IllegalStateException("Invalid JWT: scope claim is required"));
- return;
- }
-
- List> target;
- if (jwt.getValue("scope") instanceof String) {
- target =
- Stream.of(jwt.getString("scope")
- .split(delimiter))
- .collect(Collectors.toList());
- } else {
- target = jwt.getJsonArray("scope").getList();
- }
-
- if (target != null) {
- for (String scope : scopes) {
- if (!target.contains(scope)) {
- ctx.fail(403, new IllegalStateException("JWT scopes != handler scopes"));
- return;
+ protected void processException(RoutingContext ctx, Throwable exception) {
+ if (exception != null) {
+ if (exception instanceof HttpException) {
+ final int statusCode = ((HttpException) exception).getStatusCode();
+ final String payload = ((HttpException) exception).getPayload();
+
+ switch (statusCode) {
+ case 302:
+ ctx.response()
+ .putHeader(HttpHeaders.LOCATION, payload)
+ .setStatusCode(302)
+ .end("Redirecting to " + payload + ".");
+ return;
+ case 401:
+ if (!"XMLHttpRequest".equals(ctx.request().getHeader("X-Requested-With"))) {
+ setAuthenticateHeader(ctx);
}
+ ctx.fail(401, exception);
+ return;
+ default:
+ ctx.fail(statusCode, exception);
+ return;
}
}
}
- ctx.next();
+
+ // fallback 500
+ ctx.fail(exception);
+ }
+
+ protected void fail(RoutingContext ctx, int code, String msg) {
+ ctx.fail(code, new IllegalStateException(msg));
}
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/OAuth2AuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/OAuth2AuthHandlerImpl.java
index 62c8bfc7f6..95b4a50a6e 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/OAuth2AuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/OAuth2AuthHandlerImpl.java
@@ -16,57 +16,36 @@
package io.vertx.ext.web.handler.impl;
-import io.vertx.core.Future;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.auth.User;
-import io.vertx.ext.auth.VertxContextPRNG;
import io.vertx.ext.auth.audit.Marker;
import io.vertx.ext.auth.audit.SecurityAudit;
-import io.vertx.ext.auth.authentication.Credentials;
-import io.vertx.ext.auth.authentication.TokenCredentials;
-import io.vertx.ext.auth.impl.Codec;
+import io.vertx.ext.auth.common.AuthenticationContextInternal;
+import io.vertx.ext.auth.common.Session;
+import io.vertx.ext.auth.common.UserContextInternal;
+import io.vertx.ext.auth.oauth2.AbstractOAuth2Handler;
import io.vertx.ext.auth.oauth2.OAuth2Auth;
-import io.vertx.ext.auth.oauth2.OAuth2AuthorizationURL;
import io.vertx.ext.auth.oauth2.OAuth2FlowType;
import io.vertx.ext.auth.oauth2.Oauth2Credentials;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.Session;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.handler.OAuth2AuthHandler;
-import io.vertx.ext.web.impl.OrderListener;
-import io.vertx.ext.web.impl.Origin;
-import io.vertx.ext.web.impl.RoutingContextInternal;
-import io.vertx.ext.web.impl.UserContextInternal;
-
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.*;
/**
* @author Paulo Lopes
*/
-public class OAuth2AuthHandlerImpl extends HTTPAuthorizationHandler implements OAuth2AuthHandler, ScopedAuthentication, OrderListener {
+public class OAuth2AuthHandlerImpl extends AbstractOAuth2Handler implements OAuth2AuthHandler {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2AuthHandlerImpl.class);
- private final VertxContextPRNG prng;
- private final Origin callbackURL;
- private final MessageDigest sha256;
-
- private final List scopes;
- private JsonObject extraParams;
- private String prompt;
- private int pkce = -1;
- // explicit signal that tokens are handled as bearer only (meaning, no backend server known)
- private boolean bearerOnly = true;
-
private int order = -1;
private Route callback;
@@ -75,179 +54,13 @@ public OAuth2AuthHandlerImpl(Vertx vertx, OAuth2Auth authProvider, String callba
}
public OAuth2AuthHandlerImpl(Vertx vertx, OAuth2Auth authProvider, String callbackURL, String realm) {
- super(authProvider, Type.BEARER, realm);
- // get a reference to the prng
- this.prng = VertxContextPRNG.current(vertx);
- // get a reference to the sha-256 digest
- try {
- sha256 = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Cannot get instance of SHA-256 MessageDigest", e);
- }
- // process callback
- if (callbackURL != null) {
- this.callbackURL = Origin.parse(callbackURL);
- } else {
- this.callbackURL = null;
- }
- // scopes are empty by default
- this.scopes = Collections.emptyList();
+ super(vertx, authProvider, callbackURL, realm);
}
private OAuth2AuthHandlerImpl(OAuth2AuthHandlerImpl base, List scopes) {
- super(base.authProvider, Type.BEARER, base.realm);
- this.prng = base.prng;
- this.callbackURL = base.callbackURL;
- this.prompt = base.prompt;
- this.pkce = base.pkce;
- this.bearerOnly = base.bearerOnly;
-
- // get a new reference to the sha-256 digest
- try {
- sha256 = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Cannot get instance of SHA-256 MessageDigest", e);
- }
- // state copy
- if (base.extraParams != null) {
- extraParams = base.extraParams.copy();
- }
+ super(base, scopes);
this.callback = base.callback;
this.order = base.order;
- // apply the new scopes
- Objects.requireNonNull(scopes, "scopes cannot be null");
- this.scopes = scopes;
- }
-
- @Override
- public Future authenticate(RoutingContext context) {
- // when the handler is working as bearer only, then the `Authorization` header is required
- return parseAuthorization(context, !bearerOnly)
- .compose(token -> {
- // Authorization header can be null when in not in bearerOnly mode
- if (token == null) {
- // redirect request to the oauth2 server as we know nothing about this request
- if (bearerOnly) {
- // it's a failure both cases but the cause is not the same
- return Future.failedFuture("callback route is not configured.");
- }
- // when this handle is mounted as a catch all, the callback route must be configured before,
- // as it would shade the callback route. When a request matches the callback path and has the
- // method GET the exceptional case should not redirect to the oauth2 server as it would become
- // an infinite redirect loop. In this case an exception must be raised.
- if (context.request().method() == HttpMethod.GET && context.normalizedPath().equals(callbackURL.resource())) {
- LOG.warn("The callback route is shaded by the OAuth2AuthHandler, ensure the callback route is added BEFORE the OAuth2AuthHandler route!");
- return Future.failedFuture(new HttpException(500, "Infinite redirect loop [oauth2 callback]"));
- } else {
- if (context.request().method() != HttpMethod.GET) {
- // we can only redirect GET requests
- LOG.error("OAuth2 redirect attempt to non GET resource");
- return Future.failedFuture(new HttpException(405, new IllegalStateException("OAuth2 redirect attempt to non GET resource")));
- }
-
- // the redirect is processed as a failure to abort the chain
- String redirectUri = context.request().uri();
- try {
- return Future.failedFuture(new HttpException(302, authURI(context, redirectUri)));
- } catch (IllegalStateException e) {
- return Future.failedFuture(e);
- }
- }
- } else {
- // continue
- final List scopes = getScopesOrSearchMetadata(this.scopes, context);
-
- final Credentials credentials =
- scopes.size() > 0 ? new TokenCredentials(token).setScopes(scopes) : new TokenCredentials(token);
-
- final SecurityAudit audit = ((RoutingContextInternal) context).securityAudit();
- audit.credentials(credentials);
-
- return authProvider.authenticate(credentials)
- .andThen(op -> audit.audit(Marker.AUTHENTICATION, op.succeeded()))
- .recover(err -> Future.failedFuture(new HttpException(401, err)));
- }
- });
- }
-
- private String authURI(RoutingContext context, String redirectURL) {
-
- String state = null;
- String codeVerifier = null;
- String loginHint = null;
-
- final Session session = context.session();
-
- if (session == null) {
- if (pkce > 0) {
- // we can only handle PKCE with a session
- throw new IllegalStateException("OAuth2 PKCE requires a session to be present");
- }
- } else {
- // there's a session we can make this request comply to the Oauth2 spec and add an opaque state
-
- loginHint = session.get("login_hint");
- // hint will be considered at least once
- session.remove("login_hint");
-
- session
- .put("redirect_uri", redirectURL);
-
- // create a state value to mitigate replay attacks
- state = prng.nextString(6);
- // store the state in the session
- session
- .put("state", state);
-
- if (pkce > 0) {
- codeVerifier = prng.nextString(pkce);
- // store the code verifier in the session
- session
- .put("pkce", codeVerifier);
- }
- }
-
- final OAuth2AuthorizationURL config = new OAuth2AuthorizationURL();
-
- if (extraParams != null) {
- for (Map.Entry entry : extraParams) {
- if (entry.getValue() != null) {
- config.putAdditionalParameter(entry.getKey(), entry.getValue().toString());
- }
- }
- }
-
- config
- .setState(state != null ? state : redirectURL)
- .setLoginHint(loginHint)
- .setPrompt(prompt);
-
- if (callbackURL != null) {
- config.setRedirectUri(callbackURL.href());
- }
-
- final List scopes = getScopesOrSearchMetadata(this.scopes, context);
-
- if (scopes.size() > 0) {
- config.setScopes(scopes);
- }
-
- if (codeVerifier != null) {
- synchronized (sha256) {
- sha256.update(codeVerifier.getBytes(StandardCharsets.US_ASCII));
- config
- .setCodeChallenge(Codec.base64UrlEncode(sha256.digest()))
- .setCodeChallengeMethod("S256");
- }
- }
-
- return authProvider.authorizeURL(new OAuth2AuthorizationURL(config));
- }
-
- @Override
- public OAuth2AuthHandler extraParams(JsonObject extraParams) {
- this.extraParams = extraParams;
- return this;
}
@Override
@@ -264,25 +77,7 @@ public OAuth2AuthHandler withScopes(List scopes) {
Objects.requireNonNull(scopes, "scopes cannot be null");
return new OAuth2AuthHandlerImpl(this, scopes);
}
-
- @Override
- public OAuth2AuthHandler prompt(String prompt) {
- this.prompt = prompt;
- return this;
- }
-
- @Override
- public OAuth2AuthHandler pkceVerifierLength(int length) {
- if (length >= 0) {
- // requires verification
- if (length < 43 || length > 128) {
- throw new IllegalArgumentException("Length must be between 34 and 128");
- }
- }
- this.pkce = length;
- return this;
- }
-
+
@Override
public OAuth2AuthHandler setupCallback(final Route route) {
@@ -320,85 +115,6 @@ public OAuth2AuthHandler setupCallback(final Route route) {
return this;
}
- private static final Set OPENID_SCOPES = new HashSet<>();
-
- static {
- OPENID_SCOPES.add("openid");
- OPENID_SCOPES.add("profile");
- OPENID_SCOPES.add("email");
- OPENID_SCOPES.add("phone");
- OPENID_SCOPES.add("offline");
- }
-
- /**
- * The default behavior for post-authentication
- */
- @Override
- public void postAuthentication(RoutingContext ctx) {
- // the user is authenticated, however the user may not have all the required scopes
- final List scopes = getScopesOrSearchMetadata(this.scopes, ctx);
-
- if (scopes.size() > 0) {
- final User user = ctx.user().get();
- if (user == null) {
- // bad state
- ctx.fail(403, new IllegalStateException("no user in the context"));
- return;
- }
-
- if (user.principal().containsKey("scope")) {
- final String userScopes = user.principal().getString("scope");
- if (userScopes != null) {
- // user principal contains scope, a basic assertion is required to ensure that
- // the scopes present match the required ones
-
- // check if openid is active
- final boolean openId = userScopes.contains("openid");
-
- for (String scope : scopes) {
- // do not assert openid scopes if openid is active
- if (openId && OPENID_SCOPES.contains(scope)) {
- continue;
- }
-
- int idx = userScopes.indexOf(scope);
- if (idx != -1) {
- // match, but is it valid?
- if (
- (idx != 0 && userScopes.charAt(idx -1) != ' ') ||
- (idx + scope.length() != userScopes.length() && userScopes.charAt(idx + scope.length()) != ' ')) {
- // invalid scope assignment
- ctx.fail(403, new IllegalStateException("principal scope != handler scopes"));
- return;
- }
- } else {
- // invalid scope assignment
- ctx.fail(403, new IllegalStateException("principal scope != handler scopes"));
- return;
- }
- }
- }
- }
- }
- ctx.next();
- }
-
- @Override
- public boolean performsRedirect() {
- // depending on the time this method is invoked
- // we can deduct with more accuracy if a redirect is possible or not
- if (!bearerOnly) {
- // we know that a redirect is definitely possible
- // as the callback handler has been created
- return true;
- } else {
- // the callback hasn't been mounted so we need to assume
- // that if no callbackURL is provided, then there isn't
- // a redirect happening in this application
- return callbackURL != null;
- }
- }
-
@Override
public void onOrder(int order) {
// order isn't known yet, we can attempt to mount
@@ -411,14 +127,7 @@ public void onOrder(int order) {
}
}
- private void mountCallback() {
-
- callback
- .method(HttpMethod.GET)
- // we want the callback before this handler
- .order(order - 1);
-
- callback.handler(ctx -> {
+ private void callbackHandler(RoutingContext ctx) {
// Some IdP's (e.g.: AWS Cognito) returns errors as query arguments
String error = ctx.request().getParam("error");
@@ -440,9 +149,9 @@ private void mountCallback() {
String errorDescription = ctx.request().getParam("error_description");
if (errorDescription != null) {
- ctx.fail(errorCode, new IllegalStateException(error + ": " + errorDescription));
+ fail(ctx, errorCode, error + ": " + errorDescription);
} else {
- ctx.fail(errorCode, new IllegalStateException(error));
+ fail(ctx, errorCode, error);
}
return;
}
@@ -452,7 +161,7 @@ private void mountCallback() {
// code is a require value
if (code == null) {
- ctx.fail(400, new IllegalStateException("Missing code parameter"));
+ fail(ctx, 400, "Missing code parameter");
return;
}
@@ -468,7 +177,7 @@ private void mountCallback() {
// state is a required field
if (state == null) {
- ctx.fail(400, new IllegalStateException("Missing IdP state parameter to the callback endpoint"));
+ fail(ctx, 400, "Missing IdP state parameter to the callback endpoint");
return;
}
@@ -482,7 +191,7 @@ private void mountCallback() {
// if there's a state in the context they must match
if (!state.equals(ctxState)) {
// forbidden, the state is not valid (this is a replay attack)
- ctx.fail(401, new IllegalStateException("Invalid oauth2 state"));
+ fail(ctx, 401, "Invalid oauth2 state");
return;
}
@@ -500,7 +209,7 @@ private void mountCallback() {
// This must exactly match the redirect_uri passed to the authorization URL in the previous step.
credentials.setRedirectUri(callbackURL.href());
- final SecurityAudit audit = ((RoutingContextInternal) ctx).securityAudit();
+ final SecurityAudit audit = ((AuthenticationContextInternal) ctx).securityAudit();
audit.credentials(credentials);
authProvider
@@ -536,6 +245,55 @@ private void mountCallback() {
.setStatusCode(302)
.end("Redirecting to " + location + ".");
});
- });
}
+
+ private void mountCallback() {
+
+ callback
+ .method(HttpMethod.GET)
+ // we want the callback before this handler
+ .order(order - 1);
+
+ callback.handler(this::callbackHandler);
+ }
+
+
+//TODO remove duplicated code from WebAuthenticationHandlerImpl
+ /**
+ * This method is protected so custom auth handlers can override the default error handling
+ */
+ protected void processException(RoutingContext ctx, Throwable exception) {
+ if (exception != null) {
+ if (exception instanceof HttpException) {
+ final int statusCode = ((HttpException) exception).getStatusCode();
+ final String payload = ((HttpException) exception).getPayload();
+
+ switch (statusCode) {
+ case 302:
+ ctx.response()
+ .putHeader(HttpHeaders.LOCATION, payload)
+ .setStatusCode(302)
+ .end("Redirecting to " + payload + ".");
+ return;
+ case 401:
+ if (!"XMLHttpRequest".equals(ctx.request().getHeader("X-Requested-With"))) {
+ setAuthenticateHeader(ctx);
+ }
+ ctx.fail(401, exception);
+ return;
+ default:
+ ctx.fail(statusCode, exception);
+ return;
+ }
+ }
+ }
+
+ // fallback 500
+ ctx.fail(exception);
+ }
+
+ protected void fail(RoutingContext ctx, int code, String msg) {
+ ctx.fail(code, new IllegalStateException(msg));
+ }
+
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/RedirectAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/RedirectAuthHandlerImpl.java
index 8e7728320a..29e1c6b17c 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/RedirectAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/RedirectAuthHandlerImpl.java
@@ -28,7 +28,7 @@
* @author Tim Fox
* @author Paulo Lopes
*/
-public class RedirectAuthHandlerImpl extends AuthenticationHandlerImpl implements RedirectAuthHandler {
+public class RedirectAuthHandlerImpl extends WebAuthenticationHandlerImpl implements RedirectAuthHandler {
private final String loginRedirectURL;
private final String returnURLParam;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ScopedAuthentication.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ScopedAuthentication.java
index cf03a03eed..fd45b82069 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ScopedAuthentication.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/ScopedAuthentication.java
@@ -1,44 +1,20 @@
package io.vertx.ext.web.handler.impl;
-import io.vertx.ext.web.Route;
-import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.handler.AuthenticationHandler;
-
import java.util.Collections;
import java.util.List;
-import java.util.Map;
+
+import io.vertx.ext.auth.common.AuthenticationHandler;
+import io.vertx.ext.web.Route;
+import io.vertx.ext.web.RoutingContext;
/**
- * Internal interface for scope aware Authentication handlers.
- *
+ * @see io.vertx.ext.auth.common.ScopedAuthentication
* @param
- * @author Paulo Lopes
*/
-public interface ScopedAuthentication {
-
- /**
- * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token
- * request are unique to the instance.
- *
- * @param scope scope.
- * @return new instance of this interface.
- */
- SELF withScope(String scope);
-
- /**
- * Return a new instance with the internal state copied from the caller but the scopes to be requested during a token
- * request are unique to the instance.
- *
- * @param scopes scopes.
- * @return new instance of this interface.
- */
- SELF withScopes(List scopes);
+public interface ScopedAuthentication>
+ extends io.vertx.ext.auth.common.ScopedAuthentication {
- /**
- * Return the list of scopes provided as the 1st argument, unless the list is empty. In this case, the list of scopes
- * is obtained from the routing context metadata if possible. In case the metadata is not available, the list of
- * scopes is always an empty list.
- */
+ @Override
default List getScopesOrSearchMetadata(List scopes, RoutingContext ctx) {
if (!scopes.isEmpty()) {
return scopes;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SessionHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SessionHandlerImpl.java
index 165032c541..31f72a8e2a 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SessionHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SessionHandlerImpl.java
@@ -29,7 +29,7 @@
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.impl.RoutingContextInternal;
import io.vertx.ext.web.impl.Signature;
-import io.vertx.ext.web.impl.UserContextInternal;
+import io.vertx.ext.auth.common.UserContextInternal;
import io.vertx.ext.web.sstore.SessionStore;
import io.vertx.ext.web.sstore.impl.SessionInternal;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SimpleAuthenticationHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SimpleAuthenticationHandlerImpl.java
index 286a46778e..3146cef6b6 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SimpleAuthenticationHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/SimpleAuthenticationHandlerImpl.java
@@ -11,7 +11,7 @@
import java.util.function.Function;
-public class SimpleAuthenticationHandlerImpl extends AuthenticationHandlerImpl implements SimpleAuthenticationHandler {
+public class SimpleAuthenticationHandlerImpl extends WebAuthenticationHandlerImpl implements SimpleAuthenticationHandler {
private Function> authn;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/TotpAuthHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/TotpAuthHandlerImpl.java
index 03ca23e059..f8eebebdf7 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/TotpAuthHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/TotpAuthHandlerImpl.java
@@ -33,10 +33,12 @@
import io.vertx.ext.web.impl.OrderListener;
import io.vertx.ext.web.impl.RoutingContextInternal;
+import static io.vertx.ext.web.handler.HttpException.UNAUTHORIZED;
+
/**
* @author Paulo Lopes
*/
-public class TotpAuthHandlerImpl extends AuthenticationHandlerImpl implements OtpAuthHandler, OrderListener {
+public class TotpAuthHandlerImpl extends WebAuthenticationHandlerImpl implements OtpAuthHandler, OrderListener {
private final OtpKeyGenerator otpKeyGen;
@@ -63,7 +65,7 @@ public Future authenticate(RoutingContext ctx) {
final User user = ctx.user().get();
if (user == null) {
- return Future.failedFuture(new HttpException(401));
+ return Future.failedFuture(UNAUTHORIZED);
} else {
Boolean userOtp = user.get("mfa");
// user hasn't 2fa yet?
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/UserHolder.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/UserHolder.java
index c56d5f3c8d..be201e5403 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/UserHolder.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/UserHolder.java
@@ -21,7 +21,7 @@
import io.vertx.core.shareddata.ClusterSerializable;
import io.vertx.ext.auth.User;
import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.impl.UserContextInternal;
+import io.vertx.ext.auth.common.UserContextInternal;
import io.vertx.ext.web.impl.Utils;
import java.nio.charset.StandardCharsets;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthenticationHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthenticationHandlerImpl.java
new file mode 100644
index 0000000000..629fbd9039
--- /dev/null
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthenticationHandlerImpl.java
@@ -0,0 +1,53 @@
+package io.vertx.ext.web.handler.impl;
+
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.auth.authentication.AuthenticationProvider;
+import io.vertx.ext.auth.common.handler.impl.AuthenticationHandlerImpl;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.HttpException;
+
+public abstract class WebAuthenticationHandlerImpl extends AuthenticationHandlerImpl {
+
+ public WebAuthenticationHandlerImpl(T authProvider) {
+ super(authProvider, null);
+ }
+
+ public WebAuthenticationHandlerImpl(T authProvider, String mfa) {
+ super(authProvider, mfa);
+ }
+
+ // TODO remove duplicated code from WebAuthenticationHandlerImpl
+ /**
+ * This method is protected so custom auth handlers can override the default error handling
+ */
+ protected void processException(RoutingContext ctx, Throwable exception) {
+ if (exception != null) {
+ if (exception instanceof HttpException) {
+ final int statusCode = ((HttpException) exception).getStatusCode();
+ final String payload = ((HttpException) exception).getPayload();
+
+ switch (statusCode) {
+ case 302:
+ ctx.response()
+ .putHeader(HttpHeaders.LOCATION, payload)
+ .setStatusCode(302)
+ .end("Redirecting to " + payload + ".");
+ return;
+ case 401:
+ if (!"XMLHttpRequest".equals(ctx.request().getHeader("X-Requested-With"))) {
+ setAuthenticateHeader(ctx);
+ }
+ ctx.fail(401, exception);
+ return;
+ default:
+ ctx.fail(statusCode, exception);
+ return;
+ }
+ }
+ }
+
+ // fallback 500
+ ctx.fail(exception);
+ }
+
+}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthnHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthnHandlerImpl.java
index 5dd9ee27a8..d233cbe3fd 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthnHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebAuthnHandlerImpl.java
@@ -31,10 +31,10 @@
import io.vertx.ext.web.handler.WebAuthnHandler;
import io.vertx.ext.web.impl.OrderListener;
import io.vertx.ext.web.impl.Origin;
-import io.vertx.ext.web.impl.UserContextInternal;
+import io.vertx.ext.auth.common.UserContextInternal;
import io.vertx.ext.web.impl.RoutingContextInternal;
-public class WebAuthnHandlerImpl extends AuthenticationHandlerImpl implements WebAuthnHandler, OrderListener {
+public class WebAuthnHandlerImpl extends WebAuthenticationHandlerImpl implements WebAuthnHandler, OrderListener {
private static final boolean CONFORMANCE = Boolean.getBoolean("io.vertx.ext.web.fido2.conformance.tests");
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebHTTPAuthorizationHandler.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebHTTPAuthorizationHandler.java
new file mode 100644
index 0000000000..84cee37f96
--- /dev/null
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/WebHTTPAuthorizationHandler.java
@@ -0,0 +1,50 @@
+package io.vertx.ext.web.handler.impl;
+
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.auth.authentication.AuthenticationProvider;
+import io.vertx.ext.auth.common.handler.impl.HTTPAuthorizationHandler;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.HttpException;
+
+public abstract class WebHTTPAuthorizationHandler extends HTTPAuthorizationHandler {
+
+ public WebHTTPAuthorizationHandler(T authProvider, Type type, String realm) {
+ super(authProvider, type, realm);
+ }
+
+ // TODO remove duplicated code from WebAuthenticationHandlerImpl
+ /**
+ * This method is protected so custom auth handlers can override the default error handling
+ */
+ protected void processException(RoutingContext ctx, Throwable exception) {
+ if (exception != null) {
+ if (exception instanceof HttpException) {
+ final int statusCode = ((HttpException) exception).getStatusCode();
+ final String payload = ((HttpException) exception).getPayload();
+
+ switch (statusCode) {
+ case 302:
+ ctx.response()
+ .putHeader(HttpHeaders.LOCATION, payload)
+ .setStatusCode(302)
+ .end("Redirecting to " + payload + ".");
+ return;
+ case 401:
+ if (!"XMLHttpRequest".equals(ctx.request().getHeader("X-Requested-With"))) {
+ setAuthenticateHeader(ctx);
+ }
+ ctx.fail(401, exception);
+ return;
+ default:
+ ctx.fail(statusCode, exception);
+ return;
+ }
+ }
+ }
+
+ // fallback 500
+ ctx.fail(exception);
+ }
+
+
+}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java
index bd1db55aaf..fa1b0f8505 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java
@@ -22,6 +22,7 @@
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.net.impl.URIDecoder;
import io.vertx.core.net.HostAndPort;
+import io.vertx.ext.auth.common.AuthenticationHandler;
import io.vertx.ext.web.MIMEHeader;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.*;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java
index 11633d262d..0266f33596 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java
@@ -11,6 +11,7 @@
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.audit.SecurityAudit;
+import io.vertx.ext.auth.common.UserContext;
import io.vertx.ext.web.*;
import java.nio.charset.Charset;
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java
index bc5d7bb6e6..e6fdeb4ea7 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java
@@ -25,6 +25,7 @@
import io.vertx.core.http.*;
import io.vertx.core.http.impl.HttpUtils;
import io.vertx.core.impl.ContextInternal;
+import io.vertx.ext.auth.common.UserContext;
import io.vertx.ext.web.*;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.handler.impl.UserHolder;
@@ -142,6 +143,11 @@ public void next() {
}
}
+ @Override
+ public void onContinue() {
+ next();
+ }
+
private void checkHandleNoMatch() {
// Next called but no more matching routes
if (failed()) {
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextInternal.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextInternal.java
index 77ed8d2227..7cc8a09e24 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextInternal.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextInternal.java
@@ -18,7 +18,7 @@
import io.vertx.codegen.annotations.CacheReturn;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.core.buffer.Buffer;
-import io.vertx.ext.auth.audit.SecurityAudit;
+import io.vertx.ext.auth.common.AuthenticationContextInternal;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
@@ -28,7 +28,7 @@
*
* @author Paulo Lopes
*/
-public interface RoutingContextInternal extends RoutingContext {
+public interface RoutingContextInternal extends RoutingContext, AuthenticationContextInternal {
int BODY_HANDLER = 1 << 1;
int CORS_HANDLER = 1 << 2;
@@ -107,13 +107,4 @@ default String basePath() {
}
}
- /**
- * Get or Default the security audit object.
- */
- SecurityAudit securityAudit();
-
- /**
- * Get or Default the security audit object.
- */
- void setSecurityAudit(SecurityAudit securityAudit);
}
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java
index 1bfd657618..fc9f12a006 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java
@@ -25,6 +25,7 @@
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
+import io.vertx.ext.auth.common.UserContext;
import io.vertx.ext.web.*;
import java.nio.charset.Charset;
@@ -192,6 +193,11 @@ public void next() {
}
}
+ @Override
+ public void onContinue() {
+ next();
+ }
+
@Override
public boolean failed() {
return inner.failed();
diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextImpl.java
index acdd9d92ac..3deb35223c 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/UserContextImpl.java
@@ -1,263 +1,12 @@
package io.vertx.ext.web.impl;
-import io.vertx.core.Future;
-import io.vertx.core.http.HttpHeaders;
-import io.vertx.core.http.HttpMethod;
-import io.vertx.core.impl.logging.Logger;
-import io.vertx.core.impl.logging.LoggerFactory;
-import io.vertx.ext.auth.User;
-import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.Session;
-import io.vertx.ext.web.UserContext;
-import io.vertx.ext.web.handler.HttpException;
+import io.vertx.ext.auth.common.AbstractUserContext;
+import io.vertx.ext.auth.common.AuthenticationContext;
-import java.util.Objects;
+public class UserContextImpl extends AbstractUserContext {
-public class UserContextImpl implements UserContextInternal {
-
- private static final String USER_SWITCH_KEY = "__vertx.user-switch-ref";
- private static final Logger LOG = LoggerFactory.getLogger(UserContext.class);
-
- private final RoutingContext ctx;
- private User user;
-
- public UserContextImpl(RoutingContext ctx) {
- this.ctx = ctx;
- }
-
- @Override
- public void setUser(User user) {
- this.user = user;
- }
-
- @Override
- public User get() {
- return user;
- }
-
- @Override
- public UserContext loginHint(String loginHint) {
- final Session session = ctx.session();
-
- if (session == null) {
- if (loginHint == null) {
- // Fine, we don't need a session
- return this;
- }
- // we always need a session, otherwise we can't track the state of the previous user
- throw new IllegalStateException("SessionHandler not seen in the route. Sessions are required to keep the state");
- }
-
- if (loginHint == null) {
- // we're removing the hint if present
- session.remove("login_hint");
- } else {
- session
- .put("login_hint", loginHint);
- }
-
- return this;
- }
-
- @Override
- public Future refresh() {
- if (!ctx.request().method().equals(HttpMethod.GET)) {
- // we can't automate a redirect to a non-GET request
- return Future.failedFuture(new HttpException(405, "Method not allowed"));
- }
- return refresh(ctx.request().absoluteURI());
- }
-
- @Override
- public Future refresh(String redirectUri) {
- Objects.requireNonNull(redirectUri, "redirectUri cannot be null");
-
- if (user == null) {
- // we need to ensure that we already had a user, otherwise we can't switch
- LOG.debug("Impersonation can only occur after a complete authn flow.");
- return Future.failedFuture(new HttpException(401));
- }
-
- final Session session = ctx.session();
-
- if (session != null) {
- // From now on, we're changing the state
- session
- // force a session id regeneration to protect against replay attacks
- .regenerateId();
- }
-
- // remove user from the context
- this.user = null;
-
- // we should redirect the UA so this link becomes invalid
- return ctx.response()
- // disable all caching
- .putHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
- .putHeader("Pragma", "no-cache")
- .putHeader(HttpHeaders.EXPIRES, "0")
- // redirect (when there is no state, redirect to home
- .putHeader(HttpHeaders.LOCATION, redirectUri)
- .setStatusCode(302)
- .end("Redirecting to " + redirectUri + ".");
- }
-
- @Override
- public Future impersonate() {
- if (!ctx.request().method().equals(HttpMethod.GET)) {
- // we can't automate a redirect to a non-GET request
- return Future.failedFuture(new HttpException(405, "Method not allowed"));
- }
- return impersonate(ctx.request().absoluteURI());
- }
-
- @Override
- public Future impersonate(String redirectUri) {
- Objects.requireNonNull(redirectUri, "redirectUri cannot be null");
-
- if (user == null) {
- // we need to ensure that we already had a user, otherwise we can't switch
- LOG.debug("Impersonation can only occur after a complete authn flow.");
- return Future.failedFuture(new HttpException(401));
- }
-
- final Session session = ctx.session();
-
- if (session == null) {
- // we always need a session, otherwise we can't track the state of the previous user
- LOG.debug("SessionHandler not seen in the route. Sessions are required to keep the state");
- return Future.failedFuture(new HttpException(500));
- }
-
- if (session.get(USER_SWITCH_KEY) != null) {
- // we always need a session, otherwise we can't track the state of the previous user
- LOG.debug("Impersonation already in place");
- return Future.failedFuture(new HttpException(400));
- }
-
- // From now on, we're changing the state
- session
- // move the user out of the context (yet keep it in the session, so we can roll back
- .put(USER_SWITCH_KEY, user)
- // force a session id regeneration to protect against replay attacks
- .regenerateId();
-
- // remove the current user from the context to avoid any further access
- this.user = null;
-
- // we should redirect the UA so this link becomes invalid
- return ctx.response()
- // disable all caching
- .putHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
- .putHeader("Pragma", "no-cache")
- .putHeader(HttpHeaders.EXPIRES, "0")
- // redirect (when there is no state, redirect to home
- .putHeader(HttpHeaders.LOCATION, redirectUri)
- .setStatusCode(302)
- .end("Redirecting to " + redirectUri + ".");
- }
-
- @Override
- public Future restore() {
- if (!ctx.request().method().equals(HttpMethod.GET)) {
- // we can't automate a redirect to a non-GET request
- return Future.failedFuture(new HttpException(405, "Method not allowed"));
- }
- return restore(ctx.request().absoluteURI());
+ public UserContextImpl(AuthenticationContext ctx) {
+ super(ctx);
}
- @Override
- public Future restore(String redirectUri) {
- Objects.requireNonNull(redirectUri, "redirectUri cannot be null");
-
- if (user == null) {
- // we need to ensure that we already had a user, otherwise we can't switch
- LOG.debug("Impersonation can only occur after a complete authn flow.");
- return Future.failedFuture(new HttpException(401));
- }
-
- final Session session = ctx.session();
-
- if (session == null) {
- // we always need a session, otherwise we can't track the state of the previous user
- LOG.debug("SessionHandler not seen in the route. Sessions are required to keep the state");
- return Future.failedFuture(new HttpException(500));
- }
-
- if (session.get(USER_SWITCH_KEY) == null) {
- // we always need a session, otherwise we can't track the state of the previous user
- LOG.debug("No previous impersonation in place");
- return Future.failedFuture(new HttpException(400));
- }
-
- // From now on, we're changing the state
- User previousUser = session.get(USER_SWITCH_KEY);
-
- session
- // move the user out of the context (yet keep it in the session, so we can rollback
- .remove(USER_SWITCH_KEY);
- // remove the previous hint
- session
- .remove("login_hint");
-
- session
- // force a session id regeneration to protect against replay attacks
- .regenerateId();
-
- // restore it to the context
- this.user = previousUser;
-
- // we should redirect the UA so this link becomes invalid
- return ctx.response()
- // disable all caching
- .putHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
- .putHeader("Pragma", "no-cache")
- .putHeader(HttpHeaders.EXPIRES, "0")
- // redirect (when there is no state, redirect to home
- .putHeader(HttpHeaders.LOCATION, redirectUri)
- .setStatusCode(302)
- .end("Redirecting to " + redirectUri + ".");
- }
-
- @Override
- public Future logout() {
- return logout("/");
- }
-
- @Override
- public Future logout(String redirectUri) {
- Objects.requireNonNull(redirectUri, "redirectUri cannot be null");
-
- final Session session = ctx.session();
- // clear the session
- if (session != null) {
- session.destroy();
- }
-
- // clear the user
- user = null;
-
- // we should redirect the UA so this link becomes invalid
- return ctx.response()
- // disable all caching
- .putHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
- .putHeader("Pragma", "no-cache")
- .putHeader(HttpHeaders.EXPIRES, "0")
- // redirect (when there is no state, redirect to home
- .putHeader(HttpHeaders.LOCATION, redirectUri)
- .setStatusCode(302)
- .end("Redirecting to " + redirectUri + ".");
- }
-
- @Override
- public void clear() {
- final Session session = ctx.session();
- // clear the session
- if (session != null) {
- session.destroy();
- }
-
- // clear the user
- user = null;
- }
}
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthHandlerTestBase.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthHandlerTestBase.java
index c1b96ef8fa..b43a69286d 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthHandlerTestBase.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthHandlerTestBase.java
@@ -25,7 +25,7 @@
import io.vertx.ext.auth.properties.PropertyFileAuthentication;
import io.vertx.ext.auth.properties.PropertyFileAuthorization;
import io.vertx.ext.web.WebTestBase;
-import io.vertx.ext.web.impl.UserContextInternal;
+import io.vertx.ext.auth.common.UserContextInternal;
import io.vertx.ext.web.sstore.LocalSessionStore;
import io.vertx.ext.web.sstore.SessionStore;
import org.junit.AfterClass;
@@ -53,7 +53,7 @@ public void testAuthAuthoritiesFail() throws Exception {
testAuthorization("tim", true, PermissionBasedAuthorization.create("knitter"));
}
- protected abstract AuthenticationHandler createAuthHandler(AuthenticationProvider authProvider);
+ protected abstract WebAuthenticationHandler createAuthHandler(AuthenticationProvider authProvider);
protected boolean requiresSession() {
return false;
@@ -72,7 +72,7 @@ protected void testAuthorization(String username, boolean fail, Authorization au
AuthenticationProvider authNProvider = PropertyFileAuthentication.create(vertx, "login/loginusers.properties");
AuthorizationProvider authZProvider = PropertyFileAuthorization.create(vertx, "login/loginusers.properties");
- AuthenticationHandler authNHandler = createAuthHandler(authNProvider);
+ WebAuthenticationHandler authNHandler = createAuthHandler(authNProvider);
router.route().handler(rc -> {
// we need to be logged in
if (!rc.user().authenticated()) {
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthXRequestedWithTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthXRequestedWithTest.java
index 73e3c01443..7588f313ce 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthXRequestedWithTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/AuthXRequestedWithTest.java
@@ -59,7 +59,7 @@ public void testNoWwwAuthenticateForAjaxCalls() throws Exception {
}
@Override
- protected AuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
+ protected WebAuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
return BasicAuthHandler.create(authProvider);
}
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/BasicAuthHandlerTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/BasicAuthHandlerTest.java
index 3dde95ee13..fdf9480a76 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/BasicAuthHandlerTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/BasicAuthHandlerTest.java
@@ -214,7 +214,7 @@ public void testLoginFailWithBadBase64() throws Exception {
}
@Override
- protected AuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
+ protected WebAuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
return BasicAuthHandler.create(authProvider);
}
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerAndTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerAndTest.java
index cae5c94e9f..876890703e 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerAndTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerAndTest.java
@@ -17,7 +17,7 @@ public void setUp() throws Exception {
super.setUp();
authProvider = PropertyFileAuthentication.create(vertx, "login/loginusers.properties");
- AuthenticationHandler redirectAuthHandler = RedirectAuthHandler.create(authProvider);
+ WebAuthenticationHandler redirectAuthHandler = RedirectAuthHandler.create(authProvider);
// create a chain
chain = ChainAuthHandler.all()
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerTest.java
index aa959fd66a..8aa9a33ae5 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthHandlerTest.java
@@ -20,7 +20,7 @@ public void setUp() throws Exception {
super.setUp();
authProvider = PropertyFileAuthentication.create(vertx, "login/loginusers.properties");
- AuthenticationHandler redirectAuthHandler = RedirectAuthHandler.create(authProvider);
+ WebAuthenticationHandler redirectAuthHandler = RedirectAuthHandler.create(authProvider);
// create a chain
chain = ChainAuthHandler.any()
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthMixHandlerTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthMixHandlerTest.java
index f11bd26844..a82a0058d0 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthMixHandlerTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/ChainAuthMixHandlerTest.java
@@ -17,11 +17,11 @@ public class ChainAuthMixHandlerTest extends WebTestBase {
private static final User USER = User.create(new JsonObject().put("id", "paulo"));
- private final AuthenticationHandler success = SimpleAuthenticationHandler.create()
+ private final WebAuthenticationHandler success = SimpleAuthenticationHandler.create()
.authenticate(ctx -> Future.succeededFuture(USER));
- private final AuthenticationHandler failure = SimpleAuthenticationHandler.create()
+ private final WebAuthenticationHandler failure = SimpleAuthenticationHandler.create()
.authenticate(ctx -> Future.failedFuture(new HttpException(401)));
@Test
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/CustomAuthHandlerTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/CustomAuthHandlerTest.java
index ef85d71e67..ea4b4f5626 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/CustomAuthHandlerTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/CustomAuthHandlerTest.java
@@ -31,11 +31,11 @@
public class CustomAuthHandlerTest extends AuthHandlerTestBase {
@Override
- protected AuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
+ protected WebAuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
return newAuthHandler(authProvider, null);
}
- private AuthenticationHandler newAuthHandler(AuthenticationProvider authProvider, Handler exceptionProcessor) {
+ private WebAuthenticationHandler newAuthHandler(AuthenticationProvider authProvider, Handler exceptionProcessor) {
return SimpleAuthenticationHandler.create()
.authenticate(ctx -> {
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/EventbusBridgeTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/EventbusBridgeTest.java
index 540aa27958..32344054f4 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/EventbusBridgeTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/EventbusBridgeTest.java
@@ -1166,7 +1166,7 @@ public void testSendRequiresAuthorityHasnotAuthority() throws Exception {
testError(new JsonObject().put("type", "send").put("address", addr).put("body", "foo"), "access_denied");
}
- private AuthenticationHandler addLoginHandler(AuthenticationProvider authProvider) {
+ private WebAuthenticationHandler addLoginHandler(AuthenticationProvider authProvider) {
return SimpleAuthenticationHandler.create()
.authenticate(ctx -> {
if (ctx.user().get() == null) {
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/OAuth2AuthHandlerTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/OAuth2AuthHandlerTest.java
index 5db674edfb..1dc63f286a 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/OAuth2AuthHandlerTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/OAuth2AuthHandlerTest.java
@@ -16,6 +16,16 @@
package io.vertx.ext.web.handler;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
@@ -26,17 +36,10 @@
import io.vertx.ext.auth.oauth2.OAuth2Auth;
import io.vertx.ext.auth.oauth2.OAuth2Options;
import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.WebTestBase;
+import io.vertx.ext.web.handler.impl.OAuth2AuthHandlerImpl;
import io.vertx.ext.web.sstore.SessionStore;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Base64;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
/**
* @author Paulo Lopes
@@ -151,9 +154,11 @@ public void testAuthCodeFlowWithScopes() throws Exception {
// create a oauth2 handler on our domain to the callback: "http://localhost:8080/callback"
OAuth2AuthHandler oauth2Handler = OAuth2AuthHandler
- .create(vertx, oauth2, "http://localhost:8080/callback")
- // require "read" scope
- .withScope("read");
+ .create(vertx, oauth2, "http://localhost:8080/callback");
+
+ //TODO fix issue with fluent API
+ // require "read" scope
+ oauth2Handler = (OAuth2AuthHandlerImpl)oauth2Handler.withScope("read");
// setup the callback handler for receiving the callback
oauth2Handler.setupCallback(router.route("/callback"));
@@ -213,9 +218,12 @@ public void testAuthCodeFlowWithScopesInvalid() throws Exception {
// create a oauth2 handler on our domain to the callback: "http://localhost:8080/callback"
OAuth2AuthHandler oauth2Handler = OAuth2AuthHandler
- .create(vertx, oauth2, "http://localhost:8080/callback")
- // require "rea" scope (will fail)
- .withScope("rea");
+ .create(vertx, oauth2, "http://localhost:8080/callback");
+
+
+ //TODO fix issue with fluent API
+ // require "rea" scope (will fail)
+ oauth2Handler = (OAuth2AuthHandlerImpl)oauth2Handler.withScope("rea");
// setup the callback handler for receiving the callback
oauth2Handler.setupCallback(router.route("/callback"));
@@ -487,8 +495,11 @@ public void testAuthPKCECodeFlow() throws Exception {
// create a oauth2 handler on our domain to the callback: "http://localhost:8080/callback"
OAuth2AuthHandler oauth2Handler = OAuth2AuthHandler
- .create(vertx, oauth2, "http://localhost:8080/callback")
- .pkceVerifierLength(64);
+ .create(vertx, oauth2, "http://localhost:8080/callback");
+
+ //TODO fix issue with fluent API
+ oauth2Handler = (OAuth2AuthHandlerImpl)oauth2Handler.pkceVerifierLength(64);
+
// setup the callback handler for receiving the callback
oauth2Handler.setupCallback(router.route("/callback"));
// protect everything under /protected
@@ -677,7 +688,7 @@ public void testPasswordFlow() throws Exception {
latch.await();
- AuthenticationHandler oauth2Handler = BasicAuthHandler.create(oauth2);
+ WebAuthenticationHandler oauth2Handler = BasicAuthHandler.create(oauth2);
// protect everything under /protected
router.route("/protected/*").handler(oauth2Handler);
diff --git a/vertx-web/src/test/java/io/vertx/ext/web/handler/RedirectAuthHandlerTest.java b/vertx-web/src/test/java/io/vertx/ext/web/handler/RedirectAuthHandlerTest.java
index e25350ae8a..0f6332ba4e 100644
--- a/vertx-web/src/test/java/io/vertx/ext/web/handler/RedirectAuthHandlerTest.java
+++ b/vertx-web/src/test/java/io/vertx/ext/web/handler/RedirectAuthHandlerTest.java
@@ -21,6 +21,7 @@
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
+import io.vertx.ext.auth.common.AuthenticationHandler;
import io.vertx.ext.auth.properties.PropertyFileAuthentication;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
@@ -229,7 +230,7 @@ public void testRedirectWithParams() throws Exception {
}
@Override
- protected AuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
+ protected WebAuthenticationHandler createAuthHandler(AuthenticationProvider authProvider) {
return RedirectAuthHandler.create(authProvider);
}