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

Issue:
[oauth2] [authorization code] No redirect_uri must result on an error
I did not set a redirect_uri when asking for an auth. code.
Also, my client doesn't have a redirect_uri in his configuration.

This situation must results on an error instead of being redirected to the login form

Pull request:
fix(oauth2): invalid or missing redirect_uri
fixes #357

Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolver.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolver.java
index 547cc4f341..eb919187c2 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolver.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolver.java
@@ -15,16 +15,10 @@
  */
 package io.gravitee.am.gateway.handler.oauth2.request;
 
-import io.gravitee.am.gateway.handler.oauth2.exception.InvalidRequestException;
-import io.gravitee.am.gateway.handler.oauth2.exception.RedirectMismatchException;
-import io.gravitee.am.gateway.handler.oauth2.exception.UnauthorizedClientException;
-import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.reactivex.Single;
 
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.List;
 
 /**
@@ -34,34 +28,10 @@ import java.util.List;
 public class AuthorizationRequestResolver extends AbstractRequestResolver<AuthorizationRequest> {
 
     public Single<AuthorizationRequest> resolve(AuthorizationRequest authorizationRequest, Client client, User endUser) {
-        return resolveAuthorizedGrantTypes(authorizationRequest, client)
-                .flatMap(request -> resolveAuthorizedScopes(request, client, endUser))
+        return resolveAuthorizedScopes(authorizationRequest, client, endUser)
                 .flatMap(request -> resolveRedirectUri(request, client));
     }
 
-    /**
-     * To request an access token, the client obtains authorization from the resource owner.
-     * The authorization is expressed in the form of an authorization grant, which the client uses to request the access token.
-     *
-     * See <a href="https://tools.ietf.org/html/rfc6749#section-4">4.  Obtaining Authorization</a>
-     *
-     * Authorization endpoint implies that the client should at least have authorization_code ou implicit grant types.
-     *
-     * @param authorizationRequest the authorization request to resolve
-     * @param client the client which trigger the request
-     * @return the authorization request
-     */
-    private Single<AuthorizationRequest> resolveAuthorizedGrantTypes(AuthorizationRequest authorizationRequest, Client client) {
-        List<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
-        if (authorizedGrantTypes == null || authorizedGrantTypes.isEmpty()) {
-            return Single.error(new UnauthorizedClientException("Client should at least have one authorized grand type"));
-        }
-        if (!containsGrantType(authorizedGrantTypes)) {
-            return Single.error(new UnauthorizedClientException("Client must at least have authorization_code or implicit grant type enable"));
-        }
-        return Single.just(authorizationRequest);
-    }
-
     /**
      * redirect_uri request parameter is OPTIONAL, but the RFC (rfc6749) assumes that
      * the request fails due to a missing, invalid, or mismatching redirection URI.
@@ -74,62 +44,12 @@ public class AuthorizationRequestResolver extends AbstractRequestResolver<Author
      * @return the authorization request
      */
     public Single<AuthorizationRequest> resolveRedirectUri(AuthorizationRequest authorizationRequest, Client client) {
-        String redirectUri = authorizationRequest.getRedirectUri();
-        List<String> registeredClientRedirectUris = client.getRedirectUris();
-        try {
-            if (registeredClientRedirectUris != null && !registeredClientRedirectUris.isEmpty()) {
-                redirectUri = obtainMatchingRedirect(registeredClientRedirectUris, redirectUri);
-                authorizationRequest.setRedirectUri(redirectUri);
-                return Single.just(authorizationRequest);
-            } else if (redirectUri != null && !redirectUri.isEmpty()) {
-                return Single.just(authorizationRequest);
-            } else {
-                return Single.error(new InvalidRequestException("A redirect_uri must be supplied."));
-            }
-        } catch (Exception e) {
-            return Single.error(e);
-        }
-    }
-
-    private boolean containsGrantType(List<String> authorizedGrantTypes) {
-        return authorizedGrantTypes.stream()
-                .anyMatch(authorizedGrantType -> OAuth2Constants.AUTHORIZATION_CODE.equals(authorizedGrantType)
-                        || OAuth2Constants.IMPLICIT.equals(authorizedGrantType));
-    }
-
-    private String obtainMatchingRedirect(List<String> redirectUris, String requestedRedirect) {
+        final String requestedRedirectUri = authorizationRequest.getRedirectUri();
+        final List<String> registeredClientRedirectUris = client.getRedirectUris();
         // no redirect_uri parameter supplied, return the first client registered redirect uri
-        if (requestedRedirect == null) {
-            return redirectUris.iterator().next();
-        }
-
-        for (String redirectUri : redirectUris) {
-            if (redirectMatches(requestedRedirect, redirectUri)) {
-                return requestedRedirect;
-            }
-        }
-        throw new RedirectMismatchException("The redirect_uri MUST match the registered callback URL for this application");
-    }
-
-    private boolean redirectMatches(String requestedRedirect, String redirectUri) {
-        try {
-            URL req = new URL(requestedRedirect);
-            URL reg = new URL(redirectUri);
-
-            int requestedPort = req.getPort() != -1 ? req.getPort() : req.getDefaultPort();
-            int registeredPort = reg.getPort() != -1 ? reg.getPort() : reg.getDefaultPort();
-
-            boolean portsMatch = registeredPort == requestedPort;
-
-            if (reg.getProtocol().equals(req.getProtocol()) &&
-                    reg.getHost().equals(req.getHost()) &&
-                    portsMatch) {
-                return req.getPath().startsWith(reg.getPath());
-            }
-        } catch (MalformedURLException e) {
-
+        if (requestedRedirectUri == null || requestedRedirectUri.isEmpty()) {
+            authorizationRequest.setRedirectUri(registeredClientRedirectUris.iterator().next());
         }
-
-        return requestedRedirect.equals(redirectUri);
+        return Single.just(authorizationRequest);
     }
 }
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
index 2aa1cfcb66..11e9dfb6d5 100644
--- 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
@@ -17,7 +17,9 @@ package io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorizati
 
 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.RedirectMismatchException;
 import io.gravitee.am.gateway.handler.oauth2.exception.ServerErrorException;
+import io.gravitee.am.gateway.handler.oauth2.exception.UnauthorizedClientException;
 import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
 import io.gravitee.am.model.Client;
 import io.vertx.core.AsyncResult;
@@ -25,6 +27,10 @@ import io.vertx.core.Future;
 import io.vertx.core.Handler;
 import io.vertx.reactivex.ext.web.RoutingContext;
 
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
 /**
  * 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
@@ -44,14 +50,25 @@ public class AuthorizationClientHandler implements Handler<RoutingContext> {
     @Override
     public void handle(RoutingContext context) {
         final String clientId = context.request().getParam(OAuth2Constants.CLIENT_ID);
+        final String redirectUri = context.request().getParam(OAuth2Constants.REDIRECT_URI);
 
         authenticate(clientId, resultHandler -> {
             if (resultHandler.failed()) {
                 context.fail(resultHandler.cause());
                 return;
             }
-            context.put(CLIENT_CONTEXT_KEY, resultHandler.result());
-            context.next();
+            // put client in the execution context
+            Client client = resultHandler.result();
+            context.put(CLIENT_CONTEXT_KEY, client);
+
+            // additional check
+            try {
+                checkGrantTypes(client);
+                checkRedirectUri(redirectUri, client);
+                context.next();
+            } catch (Exception ex) {
+                context.fail(ex);
+            }
         });
     }
 
@@ -64,4 +81,69 @@ public class AuthorizationClientHandler implements Handler<RoutingContext> {
                         () -> authHandler.handle(Future.failedFuture(new InvalidRequestException("No client found for client_id " + clientId)))
                 );
     }
+
+    private void checkGrantTypes(Client client) {
+        // Authorization endpoint implies that the client should at least have authorization_code ou implicit grant types.
+        List<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
+        if (authorizedGrantTypes == null || authorizedGrantTypes.isEmpty()) {
+            throw new UnauthorizedClientException("Client should at least have one authorized grand type");
+        }
+        if (!containsGrantType(authorizedGrantTypes)) {
+            throw new UnauthorizedClientException("Client must at least have authorization_code or implicit grant type enable");
+        }
+    }
+
+    private boolean containsGrantType(List<String> authorizedGrantTypes) {
+        return authorizedGrantTypes.stream()
+                .anyMatch(authorizedGrantType -> OAuth2Constants.AUTHORIZATION_CODE.equals(authorizedGrantType)
+                        || OAuth2Constants.IMPLICIT.equals(authorizedGrantType));
+    }
+
+    private void checkRedirectUri(String requestedRedirectUri, Client client) {
+        final List<String> registeredClientRedirectUris = client.getRedirectUris();
+        final boolean hasRegisteredClientRedirectUris = registeredClientRedirectUris != null && !registeredClientRedirectUris.isEmpty();
+        final boolean hasRequestedRedirectUri = requestedRedirectUri != null && !requestedRedirectUri.isEmpty();
+
+        // if no requested redirect_uri and no registered client redirect_uris
+        // throw invalid request exception
+        if (!hasRegisteredClientRedirectUris && !hasRequestedRedirectUri) {
+            throw new InvalidRequestException("A redirect_uri must be supplied.");
+        }
+
+        // if requested redirect_uri doesn't match registered client redirect_uris
+        // throw redirect mismatch exception
+        if (hasRequestedRedirectUri && hasRegisteredClientRedirectUris) {
+            checkMatchingRedirectUri(requestedRedirectUri, registeredClientRedirectUris);
+        }
+    }
+
+    private void checkMatchingRedirectUri(String requestedRedirect, List<String> registeredClientRedirectUris) {
+        if (registeredClientRedirectUris
+                .stream()
+                .noneMatch(registeredClientUri -> redirectMatches(requestedRedirect, registeredClientUri))) {
+            throw new RedirectMismatchException("The redirect_uri MUST match the registered callback URL for this application");
+        }
+    }
+
+    private boolean redirectMatches(String requestedRedirect, String registeredClientUri) {
+        try {
+            URL req = new URL(requestedRedirect);
+            URL reg = new URL(registeredClientUri);
+
+            int requestedPort = req.getPort() != -1 ? req.getPort() : req.getDefaultPort();
+            int registeredPort = reg.getPort() != -1 ? reg.getPort() : reg.getDefaultPort();
+
+            boolean portsMatch = registeredPort == requestedPort;
+
+            if (reg.getProtocol().equals(req.getProtocol()) &&
+                    reg.getHost().equals(req.getHost()) &&
+                    portsMatch) {
+                return req.getPath().startsWith(reg.getPath());
+            }
+        } catch (MalformedURLException e) {
+
+        }
+
+        return requestedRedirect.equals(registeredClientUri);
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java
index fc9500f3a6..ae02987eb7 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java
@@ -21,7 +21,6 @@ import io.gravitee.am.gateway.handler.oauth2.exception.InvalidScopeException;
 import io.gravitee.am.gateway.handler.oauth2.exception.LoginRequiredException;
 import io.gravitee.am.gateway.handler.oauth2.exception.UnsupportedResponseTypeException;
 import io.gravitee.am.gateway.handler.oauth2.pkce.PKCEUtils;
-import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
 import io.gravitee.am.gateway.handler.oauth2.utils.OIDCParameters;
 import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDDiscoveryService;
@@ -29,7 +28,6 @@ import io.gravitee.am.gateway.handler.oidc.request.ClaimsRequest;
 import io.gravitee.am.gateway.handler.oidc.request.ClaimsRequestResolver;
 import io.gravitee.am.gateway.handler.oidc.request.ClaimsRequestSyntaxException;
 import io.gravitee.am.gateway.handler.utils.UriBuilder;
-import io.gravitee.am.gateway.handler.vertx.handler.oauth2.request.AuthorizationRequestFactory;
 import io.gravitee.am.model.Domain;
 import io.gravitee.common.http.HttpHeaders;
 import io.vertx.core.Handler;
@@ -57,7 +55,6 @@ import java.util.List;
 public class AuthorizationRequestParseHandler implements Handler<RoutingContext> {
 
     private static final Logger logger = LoggerFactory.getLogger(AuthorizationRequestParseHandler.class);
-    private final AuthorizationRequestFactory authorizationRequestFactory = new AuthorizationRequestFactory();
     private final ClaimsRequestResolver claimsRequestResolver = new ClaimsRequestResolver();
     private Domain domain;
     private OpenIDDiscoveryService openIDDiscoveryService;
@@ -69,23 +66,17 @@ public class AuthorizationRequestParseHandler implements Handler<RoutingContext>
 
     @Override
     public void handle(RoutingContext context) {
-        AuthorizationRequest authorizationRequest = authorizationRequestFactory.create(context.request());
-
-        // The authorization server validates the request to ensure that all required parameters are present and valid.
-        String responseType = authorizationRequest.getResponseType();
-        String clientId = authorizationRequest.getClientId();
-
         // proceed response type parameter
-        parseResponseTypeParameter(responseType);
+        parseResponseTypeParameter(context);
 
         // proceed client_id parameter
-        parseClientIdParameter(clientId);
+        parseClientIdParameter(context);
 
         // parse scope parameter
         parseScopeParameter(context);
 
         // proceed nonce parameter
-        parseNonceParameter(context, responseType);
+        parseNonceParameter(context);
 
         // proceed prompt parameter
         parsePromptParameter(context);
@@ -110,7 +101,9 @@ public class AuthorizationRequestParseHandler implements Handler<RoutingContext>
         }
     }
 
-    private void parseResponseTypeParameter(String responseType) {
+    private void parseResponseTypeParameter(RoutingContext context) {
+        String responseType = context.request().getParam(OAuth2Constants.RESPONSE_TYPE);
+
         if (responseType == null) {
             throw new InvalidRequestException("Missing parameter: response_type");
         }
@@ -122,7 +115,9 @@ public class AuthorizationRequestParseHandler implements Handler<RoutingContext>
         }
     }
 
-    private void parseClientIdParameter(String clientId) {
+    private void parseClientIdParameter(RoutingContext context) {
+        String clientId = context.request().getParam(OAuth2Constants.CLIENT_ID);
+
         if (clientId == null) {
             throw new InvalidRequestException("Missing parameter: client_id");
         }
@@ -233,8 +228,9 @@ public class AuthorizationRequestParseHandler implements Handler<RoutingContext>
         }
     }
 
-    private void parseNonceParameter(RoutingContext context, String responseType) {
+    private void parseNonceParameter(RoutingContext context) {
         String nonce = context.request().getParam(OIDCParameters.NONCE);
+        String responseType = context.request().getParam(OAuth2Constants.RESPONSE_TYPE);
         // nonce parameter is required for the Hybrid flow
         if (nonce == null && isHybridFlow(responseType)) {
             throw new InvalidRequestException("Missing parameter: nonce is required for Hybrid Flow");


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolverTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolverTest.java
index cfa95ba182..f04550f649 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolverTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/request/AuthorizationRequestResolverTest.java
@@ -16,8 +16,6 @@
 package io.gravitee.am.gateway.handler.oauth2.request;
 
 import io.gravitee.am.gateway.handler.oauth2.exception.InvalidScopeException;
-import io.gravitee.am.gateway.handler.oauth2.exception.RedirectMismatchException;
-import io.gravitee.am.gateway.handler.oauth2.exception.UnauthorizedClientException;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.Role;
 import io.gravitee.am.model.User;
@@ -50,36 +48,6 @@ public class AuthorizationRequestResolverTest {
         testObserver.assertError(InvalidScopeException.class);
     }
 
-    @Test
-    public void shouldResolveAuthorizationRequest_emptyAuthorizedGrantType() {
-        final String scope = "read";
-        final String redirectUri = "http://localhost:8080/callback";
-        AuthorizationRequest authorizationRequest = new AuthorizationRequest();
-        authorizationRequest.setScopes(Collections.singleton(scope));
-        authorizationRequest.setRedirectUri(redirectUri);
-        Client client = new Client();
-        client.setAuthorizedGrantTypes(Collections.emptyList());
-
-        TestObserver<AuthorizationRequest> testObserver = authorizationRequestResolver.resolve(authorizationRequest, client, null).test();
-        testObserver.assertNotComplete();
-        testObserver.assertError(UnauthorizedClientException.class);
-    }
-
-    @Test
-    public void shouldResolveAuthorizationRequest_invalidAuthorizedGrantType() {
-        final String scope = "read";
-        final String redirectUri = "http://localhost:8080/callback";
-        AuthorizationRequest authorizationRequest = new AuthorizationRequest();
-        authorizationRequest.setScopes(Collections.singleton(scope));
-        authorizationRequest.setRedirectUri(redirectUri);
-        Client client = new Client();
-        client.setAuthorizedGrantTypes(Collections.singletonList("client_credentials"));
-
-        TestObserver<AuthorizationRequest> testObserver = authorizationRequestResolver.resolve(authorizationRequest, client, null).test();
-        testObserver.assertNotComplete();
-        testObserver.assertError(UnauthorizedClientException.class);
-    }
-
     @Test
     public void shouldResolveAuthorizationRequest_emptyScope() {
         final String redirectUri = "http://localhost:8080/callback";
@@ -164,36 +132,4 @@ public class AuthorizationRequestResolverTest {
         testObserver.assertComplete();
         testObserver.assertNoErrors();
     }
-
-    @Test
-    public void shouldResolveAuthorizationRequest_matchingRedirectUri() {
-        final String scope = "read";
-        final String redirectUri = "http://localhost:8080/callback";
-        AuthorizationRequest authorizationRequest = new AuthorizationRequest();
-        authorizationRequest.setScopes(Collections.singleton(scope));
-        authorizationRequest.setRedirectUri(redirectUri);
-        Client client = new Client();
-        client.setScopes(Collections.singletonList(scope));
-        client.setRedirectUris(Arrays.asList("http://localhost:8080/callback", "http://localhost:8080/callback2"));
-
-        TestObserver<AuthorizationRequest> testObserver = authorizationRequestResolver.resolve(authorizationRequest, client, null).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
-    }
-
-    @Test
-    public void shouldResolveAuthorizationRequest_mismatchingRedirectUri() {
-        final String scope = "read";
-        final String redirectUri = "http://localhost:8080/callback";
-        AuthorizationRequest authorizationRequest = new AuthorizationRequest();
-        authorizationRequest.setScopes(Collections.singleton(scope));
-        authorizationRequest.setRedirectUri(redirectUri);
-        Client client = new Client();
-        client.setScopes(Collections.singletonList(scope));
-        client.setRedirectUris(Arrays.asList("http://localhost:8080/allowRedirect", "http://localhost:8080/allowRedirect2"));
-
-        TestObserver<AuthorizationRequest> testObserver = authorizationRequestResolver.resolve(authorizationRequest, client, null).test();
-        testObserver.assertNotComplete();
-        testObserver.assertError(RedirectMismatchException.class);
-    }
 }
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 880f0a65b9..ad2ffbbca7 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
@@ -19,7 +19,6 @@ import io.gravitee.am.common.oauth2.ResponseType;
 import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
 import io.gravitee.am.gateway.handler.oauth2.exception.AccessDeniedException;
 import io.gravitee.am.gateway.handler.oauth2.exception.InvalidScopeException;
-import io.gravitee.am.gateway.handler.oauth2.exception.RedirectMismatchException;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.response.*;
 import io.gravitee.am.gateway.handler.oauth2.token.AccessToken;
@@ -184,7 +183,7 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         client.setId("client-id");
         client.setClientId("client-id");
         client.setScopes(Collections.singletonList("read"));
-        client.setRedirectUris(Collections.singletonList("http://localhost:9999/authorize/callback"));
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
 
         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())));
@@ -208,26 +207,50 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
     }
 
     @Test
-    public void shouldNotInvokeAuthorizationEndpoint_mismatchRedirectUri() throws Exception {
+    public void shouldNotInvokeAuthorizationEndpoint_emptyRedirectUri() throws Exception {
         final Client client = new Client();
         client.setId("client-id");
         client.setClientId("client-id");
         client.setScopes(Collections.singletonList("read"));
-        client.setRedirectUris(Collections.singletonList("http://localhost:9999/authorize/callback"));
+
+        AuthorizationRequest authorizationRequest = new AuthorizationRequest();
+        authorizationRequest.setApproved(true);
+        authorizationRequest.setResponseType(OAuth2Constants.CODE);
 
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));
 
-        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(
+                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=A+redirect_uri+must+be+supplied.", location);
+                },
+                HttpStatusCode.FOUND_302, "Found", null);
+    }
 
-        when(flow.run(any(), any(), any())).thenReturn(Single.error(new RedirectMismatchException("The redirect_uri MUST match the registered callback URL for this application")));
+    @Test
+    public void shouldNotInvokeAuthorizationEndpoint_mismatchRedirectUri() throws Exception {
+        final Client client = new Client();
+        client.setId("client-id");
+        client.setClientId("client-id");
+        client.setScopes(Collections.singletonList("read"));
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/authorize/callback"));
+
+        AuthorizationRequest authorizationRequest = new AuthorizationRequest();
+        authorizationRequest.setApproved(true);
+        authorizationRequest.setResponseType(OAuth2Constants.CODE);
+        authorizationRequest.setRedirectUri("http://localhost:9999/wrong/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&redirect_uri=http://localhost:9999/callback",
+                "/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://localhost:9999/wrong/callback",
                 null,
                 resp -> {
                     String location = resp.headers().get("location");
@@ -650,7 +673,7 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
         client.setId("client-id");
         client.setClientId("client-id");
         client.setScopes(Collections.singletonList("read"));
-        client.setRedirectUris(Collections.singletonList("http://localhost:9999/authorize/callback"));
+        client.setRedirectUris(Collections.singletonList("http://localhost:9999/callback"));
 
         when(domain.getPath()).thenReturn("test");
         when(clientService.findByClientId("client-id")).thenReturn(Maybe.just(client));