Repository: gravitee-io/graviteeio-access-management (java)

Issue:
[oauth2] [authorization code] An unknown client / invalid client must not be redirected to login form
If I'm using an unknown client_id, I must not be redirected to the login form.

Pull request:
fix(oauth2): handle unknown client for authorization flow
fixes #353

Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/AbstractFlow.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/AbstractFlow.java
index 5b0d6bfcbb..1c8766f36a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/AbstractFlow.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/AbstractFlow.java
@@ -16,14 +16,11 @@
 package io.gravitee.am.gateway.handler.oidc.flow;
 
 import io.gravitee.am.gateway.handler.oauth2.approval.ApprovalService;
-import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
-import io.gravitee.am.gateway.handler.oauth2.exception.InvalidRequestException;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequestResolver;
 import io.gravitee.am.gateway.handler.oauth2.response.AuthorizationResponse;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
-import io.reactivex.Maybe;
 import io.reactivex.Single;
 
 import java.util.List;
@@ -37,7 +34,6 @@ public abstract class AbstractFlow implements Flow {
 
     private final List<String> responseTypes;
     private AuthorizationRequestResolver authorizationRequestResolver;
-    private ClientService clientService;
     private ApprovalService approvalService;
 
     public AbstractFlow(final List<String> responseTypes) {
@@ -51,20 +47,12 @@ public abstract class AbstractFlow implements Flow {
     }
 
     @Override
-    public Single<AuthorizationResponse> run(AuthorizationRequest authorizationRequest, User endUser) {
-        return resolveClient(authorizationRequest)
-                .flatMap(client -> handleRequest(authorizationRequest, client, endUser));
+    public Single<AuthorizationResponse> run(AuthorizationRequest authorizationRequest, Client client, User endUser) {
+        return handleRequest(authorizationRequest, client, endUser);
     }
 
     protected abstract Single<AuthorizationResponse> prepareResponse(AuthorizationRequest authorizationRequest, Client client, User endUser);
 
-    private Single<Client> resolveClient(AuthorizationRequest authorizationRequest) {
-        final String clientId = authorizationRequest.getClientId();
-        return clientService.findByClientId(clientId)
-                .switchIfEmpty(Maybe.error(new InvalidRequestException("No client with id : " + clientId)))
-                .toSingle();
-    }
-
     private Single<AuthorizationResponse> handleRequest(AuthorizationRequest authorizationRequest, Client client, User endUser) {
         return prepareRequest(authorizationRequest, client, endUser)
                 .flatMap(authorizationRequest1 -> obtainEndUserConsent(authorizationRequest1, client, endUser))
@@ -87,15 +75,6 @@ public abstract class AbstractFlow implements Flow {
         this.authorizationRequestResolver = authorizationRequestResolver;
     }
 
-    public ClientService getClientService() {
-        return clientService;
-    }
-
-    public void setClientService(ClientService clientService) {
-        this.clientService = clientService;
-    }
-
-
     public ApprovalService getApprovalService() {
         return approvalService;
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/CompositeFlow.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/CompositeFlow.java
index 6544b5d970..a2c26bbb0f 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/CompositeFlow.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/CompositeFlow.java
@@ -16,7 +16,6 @@
 package io.gravitee.am.gateway.handler.oidc.flow;
 
 import io.gravitee.am.gateway.handler.oauth2.approval.ApprovalService;
-import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
 import io.gravitee.am.gateway.handler.oauth2.code.AuthorizationCodeService;
 import io.gravitee.am.gateway.handler.oauth2.exception.UnsupportedResponseTypeException;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
@@ -27,6 +26,7 @@ import io.gravitee.am.gateway.handler.oidc.flow.authorizationcode.AuthorizationC
 import io.gravitee.am.gateway.handler.oidc.flow.hybrid.HybridFlow;
 import io.gravitee.am.gateway.handler.oidc.flow.implicit.ImplicitFlow;
 import io.gravitee.am.gateway.handler.oidc.idtoken.IDTokenService;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.reactivex.Observable;
 import io.reactivex.Single;
@@ -46,9 +46,6 @@ public class CompositeFlow implements Flow, InitializingBean  {
     private List<Flow> flows = new ArrayList<>();
     private final AuthorizationRequestResolver authorizationRequestResolver = new AuthorizationRequestResolver();
 
-    @Autowired
-    private ClientService clientService;
-
     @Autowired
     private ApprovalService approvalService;
 
@@ -67,19 +64,19 @@ public class CompositeFlow implements Flow, InitializingBean  {
     }
 
     @Override
-    public Single<AuthorizationResponse> run(AuthorizationRequest authorizationRequest, User endUser) {
+    public Single<AuthorizationResponse> run(AuthorizationRequest authorizationRequest, Client client, User endUser) {
         return Observable
                 .fromIterable(flows)
                 .filter(flow -> flow.handle(authorizationRequest.getResponseType()))
                 .switchIfEmpty(Observable.error(new UnsupportedResponseTypeException("Unsupported response type: " + authorizationRequest.getResponseType())))
-                .flatMapSingle(flow -> flow.run(authorizationRequest, endUser)).singleOrError();
+                .flatMapSingle(flow -> flow.run(authorizationRequest, client, endUser)).singleOrError();
     }
 
     @Override
     public void afterPropertiesSet() {
-        addFlow(new AuthorizationCodeFlow(authorizationRequestResolver, clientService, approvalService, authorizationCodeService));
-        addFlow(new ImplicitFlow(authorizationRequestResolver, clientService, approvalService, tokenService, idTokenService));
-        addFlow(new HybridFlow(authorizationRequestResolver, clientService, approvalService, authorizationCodeService, tokenService, idTokenService));
+        addFlow(new AuthorizationCodeFlow(authorizationRequestResolver, approvalService, authorizationCodeService));
+        addFlow(new ImplicitFlow(authorizationRequestResolver, approvalService, tokenService, idTokenService));
+        addFlow(new HybridFlow(authorizationRequestResolver, approvalService, authorizationCodeService, tokenService, idTokenService));
     }
 
     private void addFlow(Flow flow) {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/Flow.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/Flow.java
index d661c1862a..491f18dc04 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/Flow.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/Flow.java
@@ -17,6 +17,7 @@ package io.gravitee.am.gateway.handler.oidc.flow;
 
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.response.AuthorizationResponse;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.reactivex.Single;
 
@@ -28,5 +29,5 @@ public interface Flow {
 
     boolean handle(String responseType);
 
-    Single<AuthorizationResponse> run(AuthorizationRequest authorizationRequest, User endUser);
+    Single<AuthorizationResponse> run(AuthorizationRequest authorizationRequest, Client client, User endUser);
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/authorizationcode/AuthorizationCodeFlow.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/authorizationcode/AuthorizationCodeFlow.java
index 67dfa0ccd6..690d624416 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/authorizationcode/AuthorizationCodeFlow.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/authorizationcode/AuthorizationCodeFlow.java
@@ -43,10 +43,9 @@ public class AuthorizationCodeFlow extends AbstractFlow {
     private final static List<String> RESPONSE_TYPES = Collections.singletonList(ResponseType.CODE);
     private AuthorizationCodeService authorizationCodeService;
 
-    public AuthorizationCodeFlow(AuthorizationRequestResolver authorizationRequestResolver, ClientService clientService, ApprovalService approvalService, AuthorizationCodeService authorizationCodeService) {
+    public AuthorizationCodeFlow(AuthorizationRequestResolver authorizationRequestResolver, ApprovalService approvalService, AuthorizationCodeService authorizationCodeService) {
         super(RESPONSE_TYPES);
         setAuthorizationRequestResolver(authorizationRequestResolver);
-        setClientService(clientService);
         setApprovalService(approvalService);
         this.authorizationCodeService = authorizationCodeService;
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/hybrid/HybridFlow.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/hybrid/HybridFlow.java
index e444bbe133..cdcd8924b1 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/hybrid/HybridFlow.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/hybrid/HybridFlow.java
@@ -17,7 +17,6 @@ package io.gravitee.am.gateway.handler.oidc.flow.hybrid;
 
 import io.gravitee.am.common.oidc.ResponseType;
 import io.gravitee.am.gateway.handler.oauth2.approval.ApprovalService;
-import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
 import io.gravitee.am.gateway.handler.oauth2.code.AuthorizationCodeService;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequestResolver;
@@ -47,14 +46,12 @@ public class HybridFlow extends AbstractFlow {
     private IDTokenService idTokenService;
 
     public HybridFlow(AuthorizationRequestResolver authorizationRequestResolver,
-                      ClientService clientService,
                       ApprovalService approvalService,
                       AuthorizationCodeService authorizationCodeService,
                       TokenService tokenService,
                       IDTokenService idTokenService) {
         super(RESPONSE_TYPES);
         setAuthorizationRequestResolver(authorizationRequestResolver);
-        setClientService(clientService);
         setApprovalService(approvalService);
         this.authorizationCodeService = authorizationCodeService;
         this.tokenService = tokenService;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/implicit/ImplicitFlow.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/implicit/ImplicitFlow.java
index 5eec6e3f90..92be99788a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/implicit/ImplicitFlow.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/flow/implicit/ImplicitFlow.java
@@ -17,7 +17,6 @@ package io.gravitee.am.gateway.handler.oidc.flow.implicit;
 
 import io.gravitee.am.common.oauth2.ResponseType;
 import io.gravitee.am.gateway.handler.oauth2.approval.ApprovalService;
-import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequestResolver;
 import io.gravitee.am.gateway.handler.oauth2.request.OAuth2Request;
@@ -45,13 +44,11 @@ public class ImplicitFlow extends AbstractFlow {
     private IDTokenService idTokenService;
 
     public ImplicitFlow(AuthorizationRequestResolver authorizationRequestResolver,
-                        ClientService clientService,
                         ApprovalService approvalService,
                         TokenService tokenService,
                         IDTokenService idTokenService) {
         super(RESPONSE_TYPES);
         setAuthorizationRequestResolver(authorizationRequestResolver);
-        setClientService(clientService);
         setApprovalService(approvalService);
         this.tokenService = tokenService;
         this.idTokenService = idTokenService;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
index 74c6643a0b..3bb171098e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
@@ -16,7 +16,6 @@
 package io.gravitee.am.gateway.handler.vertx;
 
 import io.gravitee.am.gateway.handler.auth.UserAuthenticationManager;
-import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
 import io.gravitee.am.gateway.handler.vertx.auth.provider.UserAuthenticationProvider;
 import io.gravitee.am.gateway.handler.vertx.handler.ExceptionHandler;
 import io.gravitee.am.gateway.handler.vertx.handler.login.LoginRouter;
@@ -52,9 +51,6 @@ public class VertxSecurityDomainHandler {
     @Autowired
     private Domain domain;
 
-    @Autowired
-    private ClientService clientService;
-
     @Autowired
     private LoginRouter loginRouter;
 
@@ -72,7 +68,7 @@ public class VertxSecurityDomainHandler {
         final Router router = Router.router(vertx);
 
         // failure handler
-        router.route("/oauth/authorize").failureHandler(new AuthorizationEndpointFailureHandler(domain, clientService));
+        router.route("/oauth/authorize").failureHandler(new AuthorizationEndpointFailureHandler(domain));
         router.route().failureHandler(new ExceptionHandler());
 
         // user authentication handler
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/OAuth2Router.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/OAuth2Router.java
index d1b293eabb..0d688ba4a8 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/OAuth2Router.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/OAuth2Router.java
@@ -29,10 +29,7 @@ import io.gravitee.am.gateway.handler.vertx.auth.handler.ClientCredentialsAuthHa
 import io.gravitee.am.gateway.handler.vertx.auth.handler.RedirectAuthHandler;
 import io.gravitee.am.gateway.handler.vertx.auth.provider.ClientAuthenticationProvider;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.ErrorHandlerEndpoint;
-import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationApprovalEndpointHandler;
-import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationEndpointHandler;
-import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationRequestParseHandler;
-import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.UserApprovalEndpointHandler;
+import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.*;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.introspection.CheckTokenEndpointHandler;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.introspection.IntrospectionEndpointHandler;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.revocation.RevocationTokenEndpointHandler;
@@ -110,6 +107,7 @@ public class OAuth2Router {
         // Bind OAuth2 endpoints
         // Authorization endpoint
         AuthorizationRequestParseHandler authorizationRequestParseHandler = AuthorizationRequestParseHandler.create(domain, openIDDiscoveryService);
+        Handler<RoutingContext> authorizationClientHandler = new AuthorizationClientHandler(clientService);
         Handler<RoutingContext> authorizeEndpoint = new AuthorizationEndpointHandler(flow, domain);
         Handler<RoutingContext> authorizeApprovalEndpoint = new AuthorizationApprovalEndpointHandler(approvalService);
         Handler<RoutingContext> userApprovalEndpoint = new UserApprovalEndpointHandler(clientService, scopeService, thymeleafTemplateEngine);
@@ -130,6 +128,7 @@ public class OAuth2Router {
         // declare oauth2 routes
         router.route(HttpMethod.GET,"/authorize")
                 .handler(authorizationRequestParseHandler)
+                .handler(authorizationClientHandler)
                 .handler(userAuthHandler)
                 .handler(authorizeEndpoint);
         router.route(HttpMethod.POST, "/authorize")
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/ErrorHandlerEndpoint.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/ErrorHandlerEndpoint.java
index 7e8607e136..7d9263d023 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/ErrorHandlerEndpoint.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/ErrorHandlerEndpoint.java
@@ -22,6 +22,9 @@ import io.vertx.reactivex.core.http.HttpServerRequest;
 import io.vertx.reactivex.ext.web.RoutingContext;
 import io.vertx.reactivex.ext.web.templ.ThymeleafTemplateEngine;
 
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
@@ -40,7 +43,14 @@ public class ErrorHandlerEndpoint implements Handler<RoutingContext> {
     public void handle(RoutingContext routingContext) {
         final HttpServerRequest request = routingContext.request();
         final String error = request.getParam(ERROR_PARAM);
-        final String errorDescription = request.getParam(ERROR_DESCRIPTION_PARAM);
+        String errorDescription = request.getParam(ERROR_DESCRIPTION_PARAM);
+        if (errorDescription != null) {
+            try {
+                errorDescription = java.net.URLDecoder.decode(request.getParam(ERROR_DESCRIPTION_PARAM), StandardCharsets.UTF_8.name());
+            } catch (UnsupportedEncodingException e) {
+                // unable to decode UTF-8 encoded query parameter
+            }
+        }
         routingContext.put(ERROR_PARAM, error);
         routingContext.put(ERROR_DESCRIPTION_PARAM, errorDescription);
         engine.render(routingContext, "access_error", res -> {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationClientHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationClientHandler.java
new file mode 100644
index 0000000000..2aa1cfcb66
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationClientHandler.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization;
+
+import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
+import io.gravitee.am.gateway.handler.oauth2.exception.InvalidRequestException;
+import io.gravitee.am.gateway.handler.oauth2.exception.ServerErrorException;
+import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
+import io.gravitee.am.model.Client;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.reactivex.ext.web.RoutingContext;
+
+/**
+ * The authorization server must ensure that the client used for the Authorization Request is registered and
+ * should not redirect to login page if the client does not exist
+ *
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class AuthorizationClientHandler implements Handler<RoutingContext> {
+
+    private static final String CLIENT_CONTEXT_KEY = "client";
+    private ClientService clientService;
+
+    public AuthorizationClientHandler(ClientService clientService) {
+        this.clientService = clientService;
+    }
+
+    @Override
+    public void handle(RoutingContext context) {
+        final String clientId = context.request().getParam(OAuth2Constants.CLIENT_ID);
+
+        authenticate(clientId, resultHandler -> {
+            if (resultHandler.failed()) {
+                context.fail(resultHandler.cause());
+                return;
+            }
+            context.put(CLIENT_CONTEXT_KEY, resultHandler.result());
+            context.next();
+        });
+    }
+
+    private void authenticate(String clientId, Handler<AsyncResult<Client>> authHandler) {
+        clientService
+                .findByClientId(clientId)
+                .subscribe(
+                        client -> authHandler.handle(Future.succeededFuture(client)),
+                        error -> authHandler.handle(Future.failedFuture(new ServerErrorException("Server error: unable to find client with client_id " + clientId))),
+                        () -> authHandler.handle(Future.failedFuture(new InvalidRequestException("No client found for client_id " + clientId)))
+                );
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointFailureHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointFailureHandler.java
index b4915c3ffa..5da74ffe38 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointFailureHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointFailureHandler.java
@@ -15,8 +15,6 @@
  */
 package io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization;
 
-import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
-import io.gravitee.am.gateway.handler.oauth2.exception.InvalidRequestException;
 import io.gravitee.am.gateway.handler.oauth2.exception.OAuth2Exception;
 import io.gravitee.am.gateway.handler.oauth2.exception.RedirectMismatchException;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
@@ -24,10 +22,10 @@ import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
 import io.gravitee.am.gateway.handler.utils.UriBuilder;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.request.AuthorizationRequestFactory;
 import io.gravitee.am.gateway.handler.vertx.utils.UriBuilderRequest;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.Domain;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.HttpStatusCode;
-import io.reactivex.Maybe;
 import io.vertx.core.Handler;
 import io.vertx.reactivex.core.http.HttpServerResponse;
 import io.vertx.reactivex.ext.web.RoutingContext;
@@ -56,13 +54,12 @@ import java.util.Map;
 public class AuthorizationEndpointFailureHandler implements Handler<RoutingContext> {
 
     private static final Logger logger = LoggerFactory.getLogger(AuthorizationEndpointFailureHandler.class);
+    private static final String CLIENT_CONTEXT_KEY = "client";
     private final AuthorizationRequestFactory authorizationRequestFactory = new AuthorizationRequestFactory();
     private Domain domain;
-    private ClientService clientService;
 
-    public AuthorizationEndpointFailureHandler(Domain domain, ClientService clientService) {
+    public AuthorizationEndpointFailureHandler(Domain domain) {
         this.domain = domain;
-        this.clientService = clientService;
     }
 
     @Override
@@ -75,32 +72,17 @@ public class AuthorizationEndpointFailureHandler implements Handler<RoutingConte
                 if (throwable instanceof OAuth2Exception) {
                     OAuth2Exception oAuth2Exception = (OAuth2Exception) throwable;
                     String clientId = request.getClientId();
-                    if (clientId == null || request.getRedirectUri() == null) {
-                        // no client available or missing redirect_uri, go to default error page
+                    Client client = routingContext.get(CLIENT_CONTEXT_KEY);
+                    // no client available or missing redirect_uri, go to default error page
+                    if (clientId == null || client == null || request.getRedirectUri() == null) {
                         request.setRedirectUri(defaultProxiedOAuthErrorPage);
-                        doRedirect(routingContext.response(), buildRedirectUri(oAuth2Exception, request));
-                        return;
                     }
-                    clientService.findByClientId(clientId)
-                            .switchIfEmpty(Maybe.error(new InvalidRequestException("Client id " + clientId + " not found")))
-                            .subscribe(client -> {
-                                        if (oAuth2Exception instanceof RedirectMismatchException) {
-                                            // user set a wrong redirect_uri
-                                            // redirect to a registered (first one) client redirect_uri
-                                            request.setRedirectUri(client.getRedirectUris().iterator().next());
-                                        }
-                                        try {
-                                            doRedirect(routingContext.response(), buildRedirectUri(oAuth2Exception, request));
-                                        } catch (Exception ex) {
-                                            logger.error("Unable to redirect to redirect uri " + request.getRedirectUri(), ex);
-                                            // go to default error page
-                                            doRedirect(routingContext.response(), defaultProxiedOAuthErrorPage);
-                                        }
-                                    },
-                                    error -> {
-                                        // fail to get client, go to default error page
-                                        doRedirect(routingContext.response(), defaultProxiedOAuthErrorPage);
-                                    });
+                    // user set a wrong redirect_uri, redirect to a registered (first one) client redirect_uri
+                    if (oAuth2Exception instanceof RedirectMismatchException) {
+                        request.setRedirectUri(client.getRedirectUris().iterator().next());
+                    }
+                    // redirect user
+                    doRedirect(routingContext.response(), buildRedirectUri(oAuth2Exception, request));
                 } else {
                     logger.error("An exception occurs while handling authorization request", throwable);
                     if (routingContext.statusCode() != -1) {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointHandler.java
index de8a686999..90f02820c8 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationEndpointHandler.java
@@ -24,6 +24,7 @@ import io.gravitee.am.gateway.handler.oauth2.utils.OIDCParameters;
 import io.gravitee.am.gateway.handler.oidc.flow.Flow;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.request.AuthorizationRequestFactory;
 import io.gravitee.am.gateway.handler.vertx.utils.UriBuilderRequest;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.Domain;
 import io.gravitee.common.http.HttpHeaders;
 import io.vertx.core.Handler;
@@ -48,6 +49,7 @@ import java.util.Arrays;
 public class AuthorizationEndpointHandler implements Handler<RoutingContext> {
 
     private static final Logger logger = LoggerFactory.getLogger(AuthorizationEndpointHandler.class);
+    private static final String CLIENT_CONTEXT_KEY = "client";
     private final AuthorizationRequestFactory authorizationRequestFactory = new AuthorizationRequestFactory();
     private Flow flow;
     private Domain domain;
@@ -68,9 +70,13 @@ public class AuthorizationEndpointHandler implements Handler<RoutingContext> {
             throw new AccessDeniedException();
         }
 
+        // get client
+        Client client = context.get(CLIENT_CONTEXT_KEY);
+
+        // get resource owner
         io.gravitee.am.model.User endUser = ((io.gravitee.am.gateway.handler.vertx.auth.user.User) authenticatedUser.getDelegate()).getUser();
 
-        flow.run(request, endUser)
+        flow.run(request, client, endUser)
                 .subscribe(authorizationResponse -> {
                     try {
                         doRedirect(context.response(), authorizationResponse.buildRedirectUri());


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java
index 7c04a04de3..880f0a65b9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java
@@ -29,6 +29,7 @@ import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDDiscoveryService;
 import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDProviderMetadata;
 import io.gravitee.am.gateway.handler.oidc.flow.Flow;
 import io.gravitee.am.gateway.handler.vertx.RxWebTestBase;
+import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationClientHandler;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationEndpointFailureHandler;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationEndpointHandler;
 import io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization.AuthorizationRequestParseHandler;
@@ -64,6 +65,8 @@ import static org.mockito.Mockito.when;
 @RunWith(MockitoJUnitRunner.class)
 public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
 
+    private static final String CLIENT_CONTEXT_KEY = "client";
+
     @Mock
     private Flow flow;
 
@@ -82,7 +85,7 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        SessionHandler sessionHandler = SessionHandler.create(LocalSessionStore.create(vertx));
+        // set openid provider service
         OpenIDProviderMetadata openIDProviderMetadata = new OpenIDProviderMetadata();
         openIDProviderMetadata.setResponseTypesSupported(Arrays.asList(ResponseType.CODE,
                 ResponseType.TOKEN,
@@ -92,15 +95,29 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
                 io.gravitee.am.common.oidc.ResponseType.ID_TOKEN_TOKEN,
                 io.gravitee.am.common.oidc.ResponseType.ID_TOKEN));
         when(openIDDiscoveryService.getConfiguration(anyString())).thenReturn(openIDProviderMetadata);
-        AuthorizationRequestParseHandler authorizationRequestParseHandler = AuthorizationRequestParseHandler.create(domain, openIDDiscoveryService);
-        router.route("/oauth/authorize").handler(sessionHandler);
-        router.route(HttpMethod.GET, "/oauth/authorize").handler(authorizationRequestParseHandler).handler(authorizationEndpointHandler);
-        router.route().failureHandler(new AuthorizationEndpointFailureHandler(domain, clientService));
+
+        // set Authorization endpoint routes
+        SessionHandler sessionHandler = SessionHandler.create(LocalSessionStore.create(vertx));
+        router.route("/oauth/authorize")
+                .handler(sessionHandler);
+        router.route(HttpMethod.GET, "/oauth/authorize")
+                .handler(AuthorizationRequestParseHandler.create(domain, openIDDiscoveryService))
+                .handler(new AuthorizationClientHandler(clientService))
+                .handler(authorizationEndpointHandler);
+        router.route()
+                .failureHandler(new AuthorizationEndpointFailureHandler(domain));
     }
 
     @Test
     public void shouldNotInvokeAuthorizationEndpoint_noUser_noRedirectUri() throws Exception {
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         when(domain.getPath()).thenReturn("test");
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+
         testRequest(
                 HttpMethod.GET,
                 "/oauth/authorize?response_type=code&client_id=client-id",
@@ -145,6 +162,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.put(CLIENT_CONTEXT_KEY, client);
+            routingContext.next();
+        });
+
         testRequest(
                 HttpMethod.GET, "/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://localhost:9999/callback&scope=",
                 null,
@@ -164,17 +186,14 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         client.setScopes(Collections.singletonList("read"));
         client.setRedirectUris(Collections.singletonList("http://localhost:9999/authorize/callback"));
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
-        when(flow.run(any(), any())).thenReturn(Single.error(new InvalidScopeException("Invalid scope(s): unknown")));
+        when(flow.run(any(), any(), any())).thenReturn(Single.error(new InvalidScopeException("Invalid scope(s): unknown")));
 
         testRequest(
                 HttpMethod.GET,
@@ -199,15 +218,12 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
-        when(flow.run(any(), any())).thenReturn(Single.error(new RedirectMismatchException("The redirect_uri MUST match the registered callback URL for this application")));
+        when(flow.run(any(), any(), any())).thenReturn(Single.error(new RedirectMismatchException("The redirect_uri MUST match the registered callback URL for this application")));
 
         testRequest(
                 HttpMethod.GET,
@@ -226,17 +242,21 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         AuthorizationRequest authorizationRequest = new AuthorizationRequest();
         authorizationRequest.setApproved(false);
 
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         when(domain.getPath()).thenReturn("test");
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.put(CLIENT_CONTEXT_KEY, new Client());
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
-        when(flow.run(any(), any())).thenReturn(Single.error(new AccessDeniedException("User denied access")));
+        when(flow.run(any(), any(), any())).thenReturn(Single.error(new AccessDeniedException("User denied access")));
 
         testRequest(
                 HttpMethod.GET,
@@ -254,6 +274,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
     public void shouldInvokeAuthorizationEndpoint_responseTypeCode() throws Exception {
         io.gravitee.am.model.User user = new io.gravitee.am.model.User();
 
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         AuthorizationRequest authorizationRequest = new AuthorizationRequest();
         authorizationRequest.setApproved(true);
         authorizationRequest.setResponseType(OAuth2Constants.CODE);
@@ -263,16 +288,14 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         authorizationResponse.setRedirectUri(authorizationRequest.getRedirectUri());
         ((AuthorizationCodeResponse) authorizationResponse).setCode("test-code");
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(user)));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(user)));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         testRequest(
                 HttpMethod.GET,
@@ -288,6 +311,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
 
     @Test
     public void shouldInvokeAuthorizationEndpoint_responseTypeToken() throws Exception {
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         AuthorizationRequest authorizationRequest = new AuthorizationRequest();
         authorizationRequest.setApproved(true);
         authorizationRequest.setResponseType(OAuth2Constants.TOKEN);
@@ -299,16 +327,14 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         authorizationResponse.setRedirectUri(authorizationRequest.getRedirectUri());
         ((ImplicitResponse) authorizationResponse).setAccessToken(accessToken);
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         testRequest(
                 HttpMethod.GET,
@@ -332,6 +358,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.put(CLIENT_CONTEXT_KEY, client);
+            routingContext.next();
+        });
+
         testRequest(
                 HttpMethod.GET,
                 "/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://localhost:9999/callback&prompt=none",
@@ -358,14 +389,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                io.gravitee.am.model.User endUser = new io.gravitee.am.model.User();
-                endUser.setLoggedAt(new Date(System.currentTimeMillis()-24*60*60*1000));
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(endUser)));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            io.gravitee.am.model.User endUser = new io.gravitee.am.model.User();
+            endUser.setLoggedAt(new Date(System.currentTimeMillis()-24*60*60*1000));
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(endUser)));
+            routingContext.next();
         });
 
         // user is logged since yesterday, he must be redirected to the login page
@@ -395,14 +423,12 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                io.gravitee.am.model.User endUser = new io.gravitee.am.model.User();
-                endUser.setLoggedAt(new Date(System.currentTimeMillis()-24*60*60*1000));
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(endUser)));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            io.gravitee.am.model.User endUser = new io.gravitee.am.model.User();
+            endUser.setLoggedAt(new Date(System.currentTimeMillis()-24*60*60*1000));
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(endUser)));
+            routingContext.put(CLIENT_CONTEXT_KEY, client);
+            routingContext.next();
         });
 
         // user is logged since yesterday, he must be redirected to the login page
@@ -420,6 +446,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
 
     @Test
     public void shouldInvokeAuthorizationEndpoint_max_age() throws Exception {
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         AuthorizationRequest authorizationRequest = new AuthorizationRequest();
         authorizationRequest.setApproved(true);
         authorizationRequest.setResponseType(OAuth2Constants.CODE);
@@ -429,18 +460,16 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         authorizationResponse.setRedirectUri(authorizationRequest.getRedirectUri());
         ((AuthorizationCodeResponse) authorizationResponse).setCode("test-code");
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                io.gravitee.am.model.User endUser = new io.gravitee.am.model.User();
-                endUser.setLoggedAt(new Date(System.currentTimeMillis()- 60*1000));
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            io.gravitee.am.model.User endUser = new io.gravitee.am.model.User();
+            endUser.setLoggedAt(new Date(System.currentTimeMillis()- 60*1000));
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
 
         // user is logged for 1 min, the max_age is big enough to validate the request
@@ -466,6 +495,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.put(CLIENT_CONTEXT_KEY, client);
+            routingContext.next();
+        });
+
         testRequest(
                 HttpMethod.GET,
                 "/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://localhost:9999/callback&code_challenge_method=plain",
@@ -488,6 +522,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.put(CLIENT_CONTEXT_KEY, client);
+            routingContext.next();
+        });
+
         testRequest(
                 HttpMethod.GET,
                 "/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://localhost:9999/callback&code_challenge_method=invalid&code_challenge=challenge",
@@ -510,6 +549,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.put(CLIENT_CONTEXT_KEY, client);
+            routingContext.next();
+        });
+
         testRequest(
                 HttpMethod.GET,
                 "/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://localhost:9999/callback&code_challenge_method=plain&code_challenge=challenge",
@@ -524,6 +568,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
 
     @Test
     public void shouldNotInvokeAuthorizationEndpoint_noUser_code_challenge_valid_plain() throws Exception {
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         AuthorizationRequest authorizationRequest = new AuthorizationRequest();
         authorizationRequest.setApproved(true);
         authorizationRequest.setResponseType(OAuth2Constants.CODE);
@@ -533,16 +582,14 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         authorizationResponse.setRedirectUri(authorizationRequest.getRedirectUri());
         ((AuthorizationCodeResponse) authorizationResponse).setCode("test-code");
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         testRequest(
                 HttpMethod.GET,
@@ -558,6 +605,11 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
 
     @Test
     public void shouldNotInvokeAuthorizationEndpoint_noUser_code_challenge_valid_s256() throws Exception {
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
+
         AuthorizationRequest authorizationRequest = new AuthorizationRequest();
         authorizationRequest.setApproved(true);
         authorizationRequest.setResponseType(OAuth2Constants.CODE);
@@ -577,7 +629,8 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         testRequest(
                 HttpMethod.GET,
@@ -602,12 +655,9 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         testRequest(
@@ -622,6 +672,23 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
                 HttpStatusCode.FOUND_302, "Found", null);
     }
 
+    @Test
+    public void shouldNotInvokeAuthorizationEndpoint_invalidClient() throws Exception {
+        when(domain.getPath()).thenReturn("test");
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.empty());
+
+        testRequest(
+                HttpMethod.GET,
+                "/oauth/authorize?response_type=code&client_id=client-id",
+                null,
+                resp -> {
+                    String location = resp.headers().get("location");
+                    assertNotNull(location);
+                    assertEquals("/test/oauth/error?error=invalid_request&error_description=No+client+found+for+client_id+client-id", location);
+                },
+                HttpStatusCode.FOUND_302, "Found", null);
+    }
+
     @Test
     public void shouldInvokeAuthorizationEndpoint_hybridFlow_code_IDToken() throws Exception {
         shouldInvokeAuthorizationEndpoint_hybridFlow(io.gravitee.am.common.oidc.ResponseType.CODE_ID_TOKEN, "code=test-code&id_token=test-id-token", null, "test-id-token");
@@ -681,16 +748,14 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
             ((HybridResponse) authorizationResponse).setIdToken(idToken);
         }
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         testRequest(
                 HttpMethod.GET,
@@ -727,16 +792,14 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         }
         authorizationResponse.setRedirectUri(authorizationRequest.getRedirectUri());
 
-        router.route().order(-1).handler(new Handler<RoutingContext>() {
-            @Override
-            public void handle(RoutingContext routingContext) {
-                routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
-                routingContext.next();
-            }
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.vertx.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
         });
 
         when(domain.getPath()).thenReturn("test");
-        when(flow.run(any(), any())).thenReturn(Single.just(authorizationResponse));
+        when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         testRequest(
                 HttpMethod.GET,