Repository: gravitee-io/graviteeio-access-management
Pull request #694 (2020-01-03 13:37:37)

fix(fail-start): Added retry logic when loading repositories.
The index creation of each repository is now blocking and can throw an exception during index creation if the database is not available. 
RepositoryPluginHandler of both gateway and management api now implement a retry mecanism.

Closes gravitee-io/issues#3064
Closes gravitee-io/issues#2693


Patch:
diff --git a/Makefile b/Makefile
index 879efcd792..ecec30c6d2 100644
--- a/Makefile
+++ b/Makefile
@@ -191,6 +191,7 @@ postman: ## Run postman non regression test (require newman npm module)
 	@newman run postman/collections/graviteeio-am-login-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 	@newman run postman/collections/graviteeio-am-logout-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 	@newman run postman/collections/graviteeio-am-user-management-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
+	@newman run postman/collections/graviteeio-am-api-management-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 
 oidctest-run: oidctest-install oidctest-start ## Run openid-certification tools, using same docker network
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/authorization/AuthorizationRequestParseParametersHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/authorization/AuthorizationRequestParseParametersHandler.java
index 9cae27a647..003dd89ee8 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/authorization/AuthorizationRequestParseParametersHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/authorization/AuthorizationRequestParseParametersHandler.java
@@ -19,7 +19,6 @@ import io.gravitee.am.common.exception.oauth2.InvalidRequestException;
 import io.gravitee.am.common.oauth2.CodeChallengeMethod;
 import io.gravitee.am.common.oidc.Parameters;
 import io.gravitee.am.common.web.UriBuilder;
-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.service.pkce.PKCEUtils;
 import io.gravitee.am.gateway.handler.oidc.exception.ClaimsRequestSyntaxException;
@@ -62,9 +61,6 @@ public class AuthorizationRequestParseParametersHandler implements Handler<Routi
 
     @Override
     public void handle(RoutingContext context) {
-        // parse scope parameter
-        parseScopeParameter(context);
-
         // proceed prompt parameter
         parsePromptParameter(context);
 
@@ -80,14 +76,6 @@ public class AuthorizationRequestParseParametersHandler implements Handler<Routi
         context.next();
     }
 
-    private void parseScopeParameter(RoutingContext context) {
-        // Check scope parameter
-        String scopes = context.request().params().get(io.gravitee.am.common.oauth2.Parameters.SCOPE);
-        if (scopes != null && scopes.isEmpty()) {
-            throw new InvalidScopeException("Invalid parameter: scope must not be empty");
-        }
-    }
-
     private void parsePromptParameter(RoutingContext context) {
         String prompt = context.request().getParam(Parameters.PROMPT);
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/token/TokenRequestParseHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/token/TokenRequestParseHandler.java
index cf08df8d8c..6df128f91e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/token/TokenRequestParseHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/handler/token/TokenRequestParseHandler.java
@@ -15,9 +15,8 @@
  */
 package io.gravitee.am.gateway.handler.oauth2.resources.handler.token;
 
-import io.gravitee.am.common.oauth2.Parameters;
 import io.gravitee.am.common.exception.oauth2.InvalidRequestException;
-import io.gravitee.am.gateway.handler.oauth2.exception.InvalidScopeException;
+import io.gravitee.am.common.oauth2.Parameters;
 import io.vertx.core.Handler;
 import io.vertx.reactivex.core.MultiMap;
 import io.vertx.reactivex.ext.web.RoutingContext;
@@ -43,9 +42,6 @@ public class TokenRequestParseHandler implements Handler<RoutingContext> {
         // proceed grant_type parameter
         parseGrantTypeParameter(context);
 
-        // proceed scope parameter
-        parseScopeParameter(context);
-
         context.next();
     }
 
@@ -69,12 +65,4 @@ public class TokenRequestParseHandler implements Handler<RoutingContext> {
             throw new InvalidRequestException("Missing parameter: grant_type");
         }
     }
-
-    private void parseScopeParameter(RoutingContext context) {
-        // Check scope parameter
-        String scopes = context.request().params().get(Parameters.SCOPE);
-        if (scopes != null && scopes.isEmpty()) {
-            throw new InvalidScopeException("Invalid parameter: scope must not be empty");
-        }
-    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/AuthorizationRequestFactory.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/AuthorizationRequestFactory.java
index 64e7af7f95..5d76c171d2 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/AuthorizationRequestFactory.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/AuthorizationRequestFactory.java
@@ -70,7 +70,7 @@ public final class AuthorizationRequestFactory {
         authorizationRequest.setResponseType(request.params().get(Parameters.RESPONSE_TYPE));
         authorizationRequest.setRedirectUri(request.params().get(Parameters.REDIRECT_URI));
         String scope = request.params().get(Parameters.SCOPE);
-        authorizationRequest.setScopes(scope != null ? new HashSet<>(Arrays.asList(scope.split("\\s+"))) : null);
+        authorizationRequest.setScopes(scope != null && !scope.isEmpty() ? new HashSet<>(Arrays.asList(scope.split("\\s+"))) : null);
         authorizationRequest.setState(request.params().get(Parameters.STATE));
         authorizationRequest.setAdditionalParameters(extractAdditionalParameters(request));
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/TokenRequestFactory.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/TokenRequestFactory.java
index 6626aaeedf..5fcced93d9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/TokenRequestFactory.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/resources/request/TokenRequestFactory.java
@@ -68,10 +68,8 @@ public final class TokenRequestFactory {
         // set OAuth 2.0 information
         tokenRequest.setClientId(request.params().get(Parameters.CLIENT_ID));
         tokenRequest.setGrantType(request.params().get(Parameters.GRANT_TYPE));
-        String scopes = request.params().get(Parameters.SCOPE);
-        if (scopes != null) {
-            tokenRequest.setScopes(new HashSet<>(Arrays.asList(scopes.split("\\s+"))));
-        }
+        String scope = request.params().get(Parameters.SCOPE);
+        tokenRequest.setScopes(scope != null && !scope.isEmpty() ? new HashSet<>(Arrays.asList(scope.split("\\s+"))) : null);
         tokenRequest.setAdditionalParameters(extractAdditionalParameters(request));
         return tokenRequest;
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/impl/IntrospectionServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/impl/IntrospectionServiceImpl.java
index 3309790d24..b474bdbf20 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/impl/IntrospectionServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/impl/IntrospectionServiceImpl.java
@@ -43,7 +43,7 @@ public class IntrospectionServiceImpl implements IntrospectionService {
         return tokenService.introspect(introspectionRequest.getToken())
                 .flatMap(token -> {
                     AccessToken accessToken = (AccessToken) token;
-                    if (accessToken.getSubject() != null) {
+                    if (accessToken.getSubject() != null && !accessToken.getSubject().equals(accessToken.getClientId())) {
                         return userService
                                 .findById(accessToken.getSubject())
                                 .map(user -> convert(accessToken, user))
diff --git a/gravitee-am-gateway/gravitee-am-gateway-repository/src/main/java/io/gravitee/am/gateway/repository/plugins/RepositoryPluginHandler.java b/gravitee-am-gateway/gravitee-am-gateway-repository/src/main/java/io/gravitee/am/gateway/repository/plugins/RepositoryPluginHandler.java
index c4258a37b7..6fbe7ac5a2 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-repository/src/main/java/io/gravitee/am/gateway/repository/plugins/RepositoryPluginHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-repository/src/main/java/io/gravitee/am/gateway/repository/plugins/RepositoryPluginHandler.java
@@ -82,29 +82,28 @@ public class RepositoryPluginHandler implements PluginHandler, InitializingBean
             Assert.isAssignable(Repository.class, repositoryClass);
 
             Repository repository = createInstance((Class<Repository>) repositoryClass);
-            for(Scope scope : repository.scopes()) {
-                if (! repositories.containsKey(scope)) {
+            for (Scope scope : repository.scopes()) {
+                if (!repositories.containsKey(scope)) {
                     String requiredRepositoryType = repositoryTypeByScope.get(scope);
 
                     // Load only repository plugin for a given scope (provided in the configuration)
                     if (repository.type().equalsIgnoreCase(requiredRepositoryType)) {
-                        LOGGER.info("Repository [{}] loaded by {}", scope, repository.type());
-
-                        // Not yet loaded, let's mount the repository in application context
-                        try {
-                            ApplicationContext repoApplicationContext = pluginContextFactory.create(
-                                    new AnnotationBasedPluginContextConfigurer(plugin) {
-                                        @Override
-                                        public Set<Class<?>> configurations() {
-                                            return Collections.singleton(repository.configuration(scope));
-                                        }
-                                    });
-
-                            registerRepositoryDefinitions(repository, repoApplicationContext);
-                            repositories.put(scope, repository);
-                        } catch (Exception iae) {
-                            LOGGER.error("Unexpected error while creating context for repository instance", iae);
-                            pluginContextFactory.remove(plugin);
+
+                        boolean loaded = false;
+                        int tries = 0;
+
+                        while (! loaded) {
+                            if (tries > 0) {
+                                // Wait for 5 seconds before giving an other try
+                                Thread.sleep(5000);
+                            }
+                            loaded = loadRepository(scope, repository, plugin);
+                            tries++;
+
+
+                            if (! loaded) {
+                                LOGGER.error("Unable to load repository {} for scope {}. Retry in 5 seconds...", scope, plugin.id());
+                            }
                         }
                     } else {
                         LOGGER.debug("Scoped repository [{}] must be loaded by {}. Skipping registration",
@@ -120,6 +119,31 @@ public class RepositoryPluginHandler implements PluginHandler, InitializingBean
         }
     }
 
+    private boolean loadRepository(Scope scope, Repository repository, Plugin plugin) {
+
+        LOGGER.info("Repository [{}] loaded by {}", scope, repository.type());
+
+        // Not yet loaded, let's mount the repository in application context
+        try {
+            ApplicationContext repoApplicationContext = pluginContextFactory.create(
+                    new AnnotationBasedPluginContextConfigurer(plugin) {
+                        @Override
+                        public Set<Class<?>> configurations() {
+                            return Collections.singleton(repository.configuration(scope));
+                        }
+                    });
+
+            registerRepositoryDefinitions(repository, repoApplicationContext);
+            repositories.put(scope, repository);
+            return true;
+        } catch (Exception iae) {
+            LOGGER.error("Unexpected error while creating context for repository instance", iae);
+            pluginContextFactory.remove(plugin);
+            return false;
+        }
+    }
+
+
     private void registerRepositoryDefinitions(Repository repository, ApplicationContext repoApplicationContext) {
         DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)
                 ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/main/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProvider.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/main/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProvider.java
index 3e4ed07419..0a5dc9860d 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/main/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProvider.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/main/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProvider.java
@@ -57,12 +57,15 @@ public class DomainBasedAuthenticationProvider implements AuthenticationProvider
     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
         WebAuthenticationDetails webAuthenticationDetails = (WebAuthenticationDetails) authentication.getDetails();
         Map<String, String> details = new HashMap();
-        details.put(Claims.ip_address, webAuthenticationDetails.getRemoteAddress());
-        details.put(Claims.user_agent, webAuthenticationDetails.getUserAgent());
         details.put(Claims.domain, domain.getId());
+        if (webAuthenticationDetails != null) {
+            details.put(Claims.ip_address, webAuthenticationDetails.getRemoteAddress());
+            details.put(Claims.user_agent, webAuthenticationDetails.getUserAgent());
+        }
         Set<String> identities = domain.getIdentities();
         Iterator<String> iter = identities.iterator();
         io.gravitee.am.identityprovider.api.User user = null;
+        AuthenticationException lastException = null;
 
         // Create a end-user authentication for underlying providers associated to the domain
         io.gravitee.am.identityprovider.api.Authentication provAuthentication = new EndUserAuthentication(
@@ -75,19 +78,25 @@ public class DomainBasedAuthenticationProvider implements AuthenticationProvider
                     identityProviderManager.get(provider);
 
             if (authenticationProvider == null) {
-                throw new BadCredentialsException("Unable to load authentication provider " + provider + ", an error occurred during the initialization stage");
+                lastException = new BadCredentialsException("Unable to load authentication provider " + provider + ", an error occurred during the initialization stage");
+                continue;
             }
 
             try {
                 user = authenticationProvider.loadUserByUsername(provAuthentication).blockingGet();
                 // set user identity provider source
                 details.put(SOURCE, provider);
+                lastException = null;
             } catch (Exception ex) {
                 logger.info("Unable to authenticate user {} with provider {}", authentication.getName(), provider, ex);
-                throw new BadCredentialsException(ex.getMessage(), ex);
+                lastException = new BadCredentialsException(ex.getMessage(), ex);
             }
         }
 
+        if (lastException != null) {
+            throw lastException;
+        }
+
         if (user != null) {
             UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, provAuthentication.getCredentials(), AuthorityUtils.NO_AUTHORITIES);
             authenticationToken.setDetails(details);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/UsersResource.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/UsersResource.java
index 0dafc9b934..a53031278b 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/UsersResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/UsersResource.java
@@ -1,157 +1,157 @@
-/**
- * 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.management.handlers.management.api.resources;
-
-import io.gravitee.am.management.service.UserService;
-import io.gravitee.am.model.User;
-import io.gravitee.am.model.common.Page;
-import io.gravitee.am.service.DomainService;
-import io.gravitee.am.service.IdentityProviderService;
-import io.gravitee.am.service.authentication.crypto.password.PasswordValidator;
-import io.gravitee.am.service.exception.DomainNotFoundException;
-import io.gravitee.am.service.exception.UserInvalidException;
-import io.gravitee.am.service.model.NewUser;
-import io.gravitee.common.http.MediaType;
-import io.reactivex.Maybe;
-import io.reactivex.Observable;
-import io.reactivex.Single;
-import io.swagger.annotations.*;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
-import javax.ws.rs.*;
-import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.container.ResourceContext;
-import javax.ws.rs.container.Suspended;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import java.net.URI;
-import java.util.Comparator;
-
-/**
- * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
- * @author GraviteeSource Team
- */
-@Api(tags = {"user"})
-public class UsersResource extends AbstractResource {
-
-    private static final int MAX_USERS_SIZE_PER_PAGE = 30;
-    private static final String MAX_USERS_SIZE_PER_PAGE_STRING = "30";
-
-    @Context
-    private ResourceContext resourceContext;
-
-    @Autowired
-    private UserService userService;
-
-    @Autowired
-    private DomainService domainService;
-
-    @Autowired
-    private IdentityProviderService identityProviderService;
-
-    @Autowired
-    private PasswordValidator passwordValidator;
-
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @ApiOperation(value = "List users for a security domain")
-    @ApiResponses({
-            @ApiResponse(code = 200, message = "List users for a security domain", response = User.class, responseContainer = "Set"),
-            @ApiResponse(code = 500, message = "Internal server error")})
-    public void list(@PathParam("domain") String domain,
-                                @QueryParam("q") String query,
-                                @QueryParam("page") @DefaultValue("0") int page,
-                                @QueryParam("size") @DefaultValue(MAX_USERS_SIZE_PER_PAGE_STRING) int size,
-                                @Suspended final AsyncResponse response) {
-        domainService.findById(domain)
-                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
-                .flatMapSingle(irrelevant -> {
-                    if (query != null) {
-                        return userService.search(domain, query, Integer.min(size, MAX_USERS_SIZE_PER_PAGE));
-                    } else {
-                        return userService.findByDomain(domain, page, Integer.min(size, MAX_USERS_SIZE_PER_PAGE));
-                    }
-                })
-                .flatMap(pagedUsers ->
-                        Observable.fromIterable(pagedUsers.getData())
-                            .flatMapSingle(user -> {
-                                if (user.getSource() != null) {
-                                    return identityProviderService.findById(user.getSource())
-                                            .map(idP -> {
-                                                user.setSource(idP.getName());
-                                                return user;
-                                            })
-                                            .defaultIfEmpty(user)
-                                            .toSingle();
-                                }
-                                return Single.just(user);
-                            })
-                            .toSortedList(Comparator.comparing(User::getUsername))
-                            .map(users -> new Page(users, pagedUsers.getCurrentPage(), pagedUsers.getTotalCount()))
-                )
-                .map(users -> Response.ok(users).build())
-                .subscribe(
-                        result -> response.resume(result),
-                        error -> response.resume(error));
-    }
-
-    @POST
-    @Produces(MediaType.APPLICATION_JSON)
-    @Consumes(MediaType.APPLICATION_JSON)
-    @ApiOperation(value = "Create a user")
-    @ApiResponses({
-            @ApiResponse(code = 201, message = "User successfully created"),
-            @ApiResponse(code = 500, message = "Internal server error")})
-    public void create(
-            @PathParam("domain") String domain,
-            @ApiParam(name = "user", required = true)
-            @Valid @NotNull final NewUser newUser,
-            @Suspended final AsyncResponse response) {
-        final io.gravitee.am.identityprovider.api.User authenticatedUser = getAuthenticatedUser();
-
-        // user must have a password in no pre registration mode
-        if (!newUser.isPreRegistration() && newUser.getPassword() == null) {
-            response.resume(new UserInvalidException(("Field [password] is required")));
-            return;
-        }
-
-        // check password policy
-        if (newUser.getPassword() != null) {
-            if (!passwordValidator.validate(newUser.getPassword())) {
-                response.resume(new UserInvalidException(("Field [password] is invalid")));
-                return;
-            }
-        }
-
-        domainService.findById(domain)
-                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
-                .flatMapSingle(userProvider -> userService.create(domain, newUser, authenticatedUser))
-                .map(user -> Response
-                        .created(URI.create("/domains/" + domain + "/users/" + user.getId()))
-                        .entity(user)
-                        .build())
-                .subscribe(
-                        result -> response.resume(result),
-                        error -> response.resume(error));
-    }
-
-    @Path("{user}")
-    public UserResource getUserResource() {
-        return resourceContext.getResource(UserResource.class);
-    }
-}
+/**
+ * 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.management.handlers.management.api.resources;
+
+import io.gravitee.am.management.service.UserService;
+import io.gravitee.am.model.User;
+import io.gravitee.am.model.common.Page;
+import io.gravitee.am.service.DomainService;
+import io.gravitee.am.service.IdentityProviderService;
+import io.gravitee.am.service.authentication.crypto.password.PasswordValidator;
+import io.gravitee.am.service.exception.DomainNotFoundException;
+import io.gravitee.am.service.exception.UserInvalidException;
+import io.gravitee.am.service.model.NewUser;
+import io.gravitee.common.http.MediaType;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.swagger.annotations.*;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.Comparator;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Api(tags = {"user"})
+public class UsersResource extends AbstractResource {
+
+    private static final int MAX_USERS_SIZE_PER_PAGE = 30;
+    private static final String MAX_USERS_SIZE_PER_PAGE_STRING = "30";
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private DomainService domainService;
+
+    @Autowired
+    private IdentityProviderService identityProviderService;
+
+    @Autowired
+    private PasswordValidator passwordValidator;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "List users for a security domain")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "List users for a security domain", response = User.class, responseContainer = "Set"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void list(@PathParam("domain") String domain,
+                                @QueryParam("q") String query,
+                                @QueryParam("page") @DefaultValue("0") int page,
+                                @QueryParam("size") @DefaultValue(MAX_USERS_SIZE_PER_PAGE_STRING) int size,
+                                @Suspended final AsyncResponse response) {
+        domainService.findById(domain)
+                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                .flatMapSingle(irrelevant -> {
+                    if (query != null) {
+                        return userService.search(domain, query, page, Integer.min(size, MAX_USERS_SIZE_PER_PAGE));
+                    } else {
+                        return userService.findByDomain(domain, page, Integer.min(size, MAX_USERS_SIZE_PER_PAGE));
+                    }
+                })
+                .flatMap(pagedUsers ->
+                        Observable.fromIterable(pagedUsers.getData())
+                            .flatMapSingle(user -> {
+                                if (user.getSource() != null) {
+                                    return identityProviderService.findById(user.getSource())
+                                            .map(idP -> {
+                                                user.setSource(idP.getName());
+                                                return user;
+                                            })
+                                            .defaultIfEmpty(user)
+                                            .toSingle();
+                                }
+                                return Single.just(user);
+                            })
+                            .toSortedList(Comparator.comparing(User::getUsername))
+                            .map(users -> new Page(users, pagedUsers.getCurrentPage(), pagedUsers.getTotalCount()))
+                )
+                .map(users -> Response.ok(users).build())
+                .subscribe(
+                        result -> response.resume(result),
+                        error -> response.resume(error));
+    }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Create a user")
+    @ApiResponses({
+            @ApiResponse(code = 201, message = "User successfully created"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void create(
+            @PathParam("domain") String domain,
+            @ApiParam(name = "user", required = true)
+            @Valid @NotNull final NewUser newUser,
+            @Suspended final AsyncResponse response) {
+        final io.gravitee.am.identityprovider.api.User authenticatedUser = getAuthenticatedUser();
+
+        // user must have a password in no pre registration mode
+        if (!newUser.isPreRegistration() && newUser.getPassword() == null) {
+            response.resume(new UserInvalidException(("Field [password] is required")));
+            return;
+        }
+
+        // check password policy
+        if (newUser.getPassword() != null) {
+            if (!passwordValidator.validate(newUser.getPassword())) {
+                response.resume(new UserInvalidException(("Field [password] is invalid")));
+                return;
+            }
+        }
+
+        domainService.findById(domain)
+                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                .flatMapSingle(userProvider -> userService.create(domain, newUser, authenticatedUser))
+                .map(user -> Response
+                        .created(URI.create("/domains/" + domain + "/users/" + user.getId()))
+                        .entity(user)
+                        .build())
+                .subscribe(
+                        result -> response.resume(result),
+                        error -> response.resume(error));
+    }
+
+    @Path("{user}")
+    public UserResource getUserResource() {
+        return resourceContext.getResource(UserResource.class);
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/plugin/RepositoryPluginHandler.java b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/plugin/RepositoryPluginHandler.java
index 49fd1e6154..c8f0798f37 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/plugin/RepositoryPluginHandler.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/plugin/RepositoryPluginHandler.java
@@ -86,23 +86,23 @@ public class RepositoryPluginHandler implements PluginHandler, InitializingBean
 
             for(Scope scope : scopes) {
                 if (! repositories.containsKey(scope)) {
-                    // Not yet loaded, let's mount the repository in application context
-                    try {
-                        ApplicationContext applicationContext = pluginContextFactory.create(
-                                new AnnotationBasedPluginContextConfigurer(plugin) {
-                                    @Override
-                                    public Set<Class<?>> configurations() {
-                                        return Collections.singleton(repository.configuration(scope));
-                                    }
-                                });
-
-                        registerRepositoryDefinitions(repository, applicationContext);
-                        repositories.put(scope, repository);
-                    } catch (Exception iae) {
-                        LOGGER.error("Unexpected error while creating context for repository instance", iae);
-                        pluginContextFactory.remove(plugin);
-                    }
 
+                    boolean loaded = false;
+                    int tries = 0;
+
+                    while (! loaded) {
+                        if (tries > 0) {
+                            // Wait for 5 seconds before giving an other try
+                            Thread.sleep(5000);
+                        }
+                        loaded = loadRepository(scope, repository, plugin);
+                        tries++;
+
+
+                        if (! loaded) {
+                            LOGGER.error("Unable to load repository {} for scope {}. Retry in 5 seconds...", scope, plugin.id());
+                        }
+                    }
                 } else {
                     LOGGER.warn("Repository scope {} already loaded by {}", scope,
                             repositories.get(scope));
@@ -113,6 +113,31 @@ public class RepositoryPluginHandler implements PluginHandler, InitializingBean
         }
     }
 
+    private boolean loadRepository(Scope scope, Repository repository, Plugin plugin) {
+        LOGGER.info("Repository [{}] loaded by {}", scope, repository.type());
+
+        // Not yet loaded, let's mount the repository in application context
+        try {
+            ApplicationContext applicationContext = pluginContextFactory.create(
+                    new AnnotationBasedPluginContextConfigurer(plugin) {
+                        @Override
+                        public Set<Class<?>> configurations() {
+                            return Collections.singleton(repository.configuration(scope));
+                        }
+                    });
+
+            registerRepositoryDefinitions(repository, applicationContext);
+            repositories.put(scope, repository);
+
+            return true;
+        } catch (Exception iae) {
+            LOGGER.error("Unexpected error while creating context for repository instance", iae);
+            pluginContextFactory.remove(plugin);
+
+            return false;
+        }
+    }
+
     private void registerRepositoryDefinitions(Repository repository, ApplicationContext repoApplicationContext) {
         DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)
                 ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
diff --git a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/UserRepositoryProxy.java b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/UserRepositoryProxy.java
index 4ac215228c..49bfedea63 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/UserRepositoryProxy.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/UserRepositoryProxy.java
@@ -44,8 +44,8 @@ public class UserRepositoryProxy extends AbstractProxy<UserRepository> implement
     }
 
     @Override
-    public Single<Page<User>> search(String domain, String query, int limit) {
-        return target.search(domain, query, limit);
+    public Single<Page<User>> search(String domain, String query, int page, int size) {
+        return target.search(domain, query, page, size);
     }
 
     @Override
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java
index 2444121ef0..de4fa868e5 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java
@@ -31,7 +31,7 @@ import java.util.List;
  */
 public interface UserService {
 
-    Single<Page<User>> search(String domain, String query, int limit);
+    Single<Page<User>> search(String domain, String query, int page, int size);
 
     Single<Page<User>> findByDomain(String domain, int page, int size);
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java
index 12eb658a23..34e9085e44 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java
@@ -100,8 +100,8 @@ public class UserServiceImpl implements UserService {
     private RoleService roleService;
 
     @Override
-    public Single<Page<User>> search(String domain, String query, int limit) {
-        return userService.search(domain, query, limit);
+    public Single<Page<User>> search(String domain, String query, int page, int size) {
+        return userService.search(domain, query, page, size);
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java
index 1bf36ac05e..32fc884a35 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java
@@ -34,7 +34,7 @@ public interface UserRepository extends CrudRepository<User, String> {
 
     Single<Page<User>> findByDomain(String domain, int page, int size);
 
-    Single<Page<User>> search(String domain, String query, int limit);
+    Single<Page<User>> search(String domain, String query, int page, int size);
 
     Single<List<User>> findByDomainAndEmail(String domain, String email, boolean strict);
 
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/common/AbstractMongoRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/common/AbstractMongoRepository.java
new file mode 100644
index 0000000000..73a0f7c04f
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/common/AbstractMongoRepository.java
@@ -0,0 +1,43 @@
+/**
+ * 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.repository.mongodb.common;
+
+import com.mongodb.client.model.IndexOptions;
+import com.mongodb.reactivestreams.client.MongoCollection;
+import com.mongodb.reactivestreams.client.MongoDatabase;
+import io.reactivex.Single;
+import org.bson.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+public abstract class AbstractMongoRepository {
+
+    private final Logger logger = LoggerFactory.getLogger(AbstractMongoRepository.class);
+
+    public void createIndex(MongoCollection<?> collection, Document document) {
+
+       this.createIndex(collection, document, new IndexOptions());
+    }
+
+    public void createIndex(MongoCollection<?> collection, Document document, IndexOptions indexOptions) {
+
+        Single.fromPublisher(collection.createIndex(document, indexOptions))
+                .doOnSuccess(s -> logger.debug("Created an index named: {}", s))
+                .doOnError(throwable -> logger.error("Error occurs during creation of index",  throwable)).blockingGet();
+    }
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/common/LoggableIndexSubscriber.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/common/LoggableIndexSubscriber.java
deleted file mode 100644
index adbd1e8337..0000000000
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/common/LoggableIndexSubscriber.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * 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.repository.mongodb.common;
-
-import io.reactivex.subscribers.DefaultSubscriber;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * @author David BRASSELY (david.brassely at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class LoggableIndexSubscriber extends DefaultSubscriber<String> {
-
-    private final Logger logger = LoggerFactory.getLogger(LoggableIndexSubscriber.class);
-
-    @Override
-    public void onNext(String value) {
-        logger.debug("Created an index named : " + value);
-    }
-
-    @Override
-    public void onError(Throwable throwable) {
-        logger.error("Error occurs during indexing", throwable);
-    }
-
-    @Override
-    public void onComplete() {
-        logger.debug("Index creation complete");
-    }
-}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java
index 91a12fca9d..ff10ec8799 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java
@@ -15,7 +15,14 @@
  */
 package io.gravitee.am.repository.mongodb.management;
 
+import com.mongodb.client.model.IndexOptions;
+import com.mongodb.reactivestreams.client.MongoCollection;
 import com.mongodb.reactivestreams.client.MongoDatabase;
+import io.gravitee.am.repository.mongodb.common.AbstractMongoRepository;
+import io.reactivex.Single;
+import org.bson.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
@@ -24,7 +31,9 @@ import org.springframework.beans.factory.annotation.Qualifier;
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
  */
-public abstract class AbstractManagementMongoRepository {
+public abstract class AbstractManagementMongoRepository extends AbstractMongoRepository {
+
+    private final Logger logger = LoggerFactory.getLogger(AbstractManagementMongoRepository.class);
 
     @Autowired
     @Qualifier("managementMongoTemplate")
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java
index fd03e1444d..47c7ce87d6 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Certificate;
 import io.gravitee.am.repository.management.api.CertificateRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.CertificateMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -51,7 +50,7 @@ public class MongoCertificateRepository extends AbstractManagementMongoRepositor
     @PostConstruct
     public void init() {
         certificatesCollection = mongoOperations.getCollection("certificates", CertificateMongo.class);
-        certificatesCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(certificatesCollection, new Document(FIELD_DOMAIN, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoClientRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoClientRepository.java
index fa7ebda3df..f14ccfac84 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoClientRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoClientRepository.java
@@ -26,7 +26,6 @@ import io.gravitee.am.model.common.Page;
 import io.gravitee.am.model.jose.*;
 import io.gravitee.am.model.oidc.JWKSet;
 import io.gravitee.am.repository.management.api.ClientRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.AccountSettingsMongo;
 import io.gravitee.am.repository.mongodb.management.internal.model.ClientMongo;
 import io.gravitee.am.repository.mongodb.management.internal.model.JWKMongo;
@@ -69,12 +68,12 @@ public class MongoClientRepository extends AbstractManagementMongoRepository imp
     @PostConstruct
     public void init() {
         clientsCollection = mongoOperations.getCollection("clients", ClientMongo.class);
-        clientsCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        clientsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        clientsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_GRANT_TYPES, 1)).subscribe(new LoggableIndexSubscriber());
-        clientsCollection.createIndex(new Document(FIELD_IDENTITIES, 1)).subscribe(new LoggableIndexSubscriber());
-        clientsCollection.createIndex(new Document(FIELD_CERTIFICATE, 1)).subscribe(new LoggableIndexSubscriber());
-        clientsCollection.createIndex(new Document(FIELD_GRANT_TYPES, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(clientsCollection, new Document(FIELD_DOMAIN, 1));
+        super.createIndex(clientsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT_ID, 1));
+        super.createIndex(clientsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_GRANT_TYPES, 1));
+        super.createIndex(clientsCollection, new Document(FIELD_IDENTITIES, 1));
+        super.createIndex(clientsCollection, new Document(FIELD_CERTIFICATE, 1));
+        super.createIndex(clientsCollection, new Document(FIELD_GRANT_TYPES, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEmailRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEmailRepository.java
index b6db37b02a..eab77381ae 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEmailRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEmailRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Email;
 import io.gravitee.am.repository.management.api.EmailRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.EmailMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -50,9 +49,9 @@ public class MongoEmailRepository extends AbstractManagementMongoRepository impl
     @PostConstruct
     public void init() {
         emailsCollection = mongoOperations.getCollection("emails", EmailMongo.class);
-        emailsCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        emailsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_TEMPLATE, 1)).subscribe(new LoggableIndexSubscriber());
-        emailsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_TEMPLATE, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(emailsCollection, new Document(FIELD_DOMAIN, 1));
+        super.createIndex(emailsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_TEMPLATE, 1));
+        super.createIndex(emailsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_TEMPLATE, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEventRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEventRepository.java
index 329ec5cd84..41ecce3fb8 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEventRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEventRepository.java
@@ -22,7 +22,6 @@ import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.model.common.event.Payload;
 import io.gravitee.am.common.event.Type;
 import io.gravitee.am.repository.management.api.EventRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.EventMongo;
 import io.reactivex.*;
 import org.bson.Document;
@@ -51,7 +50,7 @@ public class MongoEventRepository extends AbstractManagementMongoRepository impl
     @PostConstruct
     public void init() {
         eventsCollection = mongoOperations.getCollection("events", EventMongo.class);
-        eventsCollection.createIndex(new Document(FIELD_UPDATED_AT, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(eventsCollection, new Document(FIELD_UPDATED_AT, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoExtensionGrantRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoExtensionGrantRepository.java
index 7367e373d7..57389cbe15 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoExtensionGrantRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoExtensionGrantRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.ExtensionGrant;
 import io.gravitee.am.repository.management.api.ExtensionGrantRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.ExtensionGrantMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -50,8 +49,8 @@ public class MongoExtensionGrantRepository extends AbstractManagementMongoReposi
     @PostConstruct
     public void init() {
         extensionGrantsCollection = mongoOperations.getCollection("extension_grants", ExtensionGrantMongo.class);
-        extensionGrantsCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        extensionGrantsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_NAME, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(extensionGrantsCollection, new Document(FIELD_DOMAIN, 1));
+        super.createIndex(extensionGrantsCollection,new Document(FIELD_DOMAIN, 1).append(FIELD_NAME, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFormRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFormRepository.java
index 6a7eaa75a6..b9666f9694 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFormRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFormRepository.java
@@ -19,14 +19,12 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Form;
 import io.gravitee.am.repository.management.api.FormRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.FormMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Observable;
 import io.reactivex.Single;
 import org.bson.Document;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
@@ -51,9 +49,9 @@ public class MongoFormRepository extends AbstractManagementMongoRepository imple
     @PostConstruct
     public void init() {
         formsCollection = mongoOperations.getCollection("forms", FormMongo.class);
-        formsCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        formsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_TEMPLATE, 1)).subscribe(new LoggableIndexSubscriber());
-        formsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_TEMPLATE, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(formsCollection,new Document(FIELD_DOMAIN, 1));
+        super.createIndex(formsCollection,new Document(FIELD_DOMAIN, 1).append(FIELD_TEMPLATE, 1));
+        super.createIndex(formsCollection,new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_TEMPLATE, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoGroupRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoGroupRepository.java
index de7d6f7153..d90c47e0c2 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoGroupRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoGroupRepository.java
@@ -21,7 +21,6 @@ import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Group;
 import io.gravitee.am.model.common.Page;
 import io.gravitee.am.repository.management.api.GroupRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.GroupMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -53,8 +52,8 @@ public class MongoGroupRepository extends AbstractManagementMongoRepository impl
     @PostConstruct
     public void init() {
         groupsCollection = mongoOperations.getCollection("groups", GroupMongo.class);
-        groupsCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        groupsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_NAME, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(groupsCollection, new Document(FIELD_DOMAIN, 1));
+        super.createIndex(groupsCollection,new Document(FIELD_DOMAIN, 1).append(FIELD_NAME, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoIdentityProviderRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoIdentityProviderRepository.java
index b0c4a43c58..3339345f82 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoIdentityProviderRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoIdentityProviderRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.IdentityProvider;
 import io.gravitee.am.repository.management.api.IdentityProviderRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.IdentityProviderMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -48,7 +47,7 @@ public class MongoIdentityProviderRepository extends AbstractManagementMongoRepo
     @PostConstruct
     public void init() {
         identitiesCollection = mongoOperations.getCollection("identities", IdentityProviderMongo.class);
-        identitiesCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(identitiesCollection,new Document(FIELD_DOMAIN, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoLoginAttemptRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoLoginAttemptRepository.java
index 8b2249c092..43aba75e23 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoLoginAttemptRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoLoginAttemptRepository.java
@@ -22,7 +22,6 @@ import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.LoginAttempt;
 import io.gravitee.am.repository.management.api.LoginAttemptRepository;
 import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.LoginAttemptMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -58,10 +57,10 @@ public class MongoLoginAttemptRepository extends AbstractManagementMongoReposito
     @PostConstruct
     public void init() {
         loginAttemptsCollection = mongoOperations.getCollection("login_attempts", LoginAttemptMongo.class);
-        loginAttemptsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_USERNAME, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(loginAttemptsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_USERNAME, 1));
 
         // expire after index
-        loginAttemptsCollection.createIndex(new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(loginAttemptsCollection, new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoPolicyRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoPolicyRepository.java
index b234d340fe..4a082ed7b0 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoPolicyRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoPolicyRepository.java
@@ -20,7 +20,6 @@ import io.gravitee.am.common.policy.ExtensionPoint;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Policy;
 import io.gravitee.am.repository.management.api.PolicyRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.PolicyMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -49,7 +48,7 @@ public class MongoPolicyRepository extends AbstractManagementMongoRepository imp
     @PostConstruct
     public void init() {
         policiesCollection = mongoOperations.getCollection("policies", PolicyMongo.class);
-        policiesCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(policiesCollection, new Document(FIELD_DOMAIN, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoReporterRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoReporterRepository.java
index 879d9bb148..089cd1d9c2 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoReporterRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoReporterRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Reporter;
 import io.gravitee.am.repository.management.api.ReporterRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.ReporterMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -48,7 +47,7 @@ public class MongoReporterRepository extends AbstractManagementMongoRepository i
     @PostConstruct
     public void init() {
         reportersCollection = mongoOperations.getCollection("reporters", ReporterMongo.class);
-        reportersCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(reportersCollection, new Document(FIELD_DOMAIN, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java
index c57c2f5de6..4572d79631 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.Role;
 import io.gravitee.am.repository.management.api.RoleRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.RoleMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -50,7 +49,7 @@ public class MongoRoleRepository extends AbstractManagementMongoRepository imple
     @PostConstruct
     public void init() {
         rolesCollection = mongoOperations.getCollection("roles", RoleMongo.class);
-        rolesCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(rolesCollection, new Document(FIELD_DOMAIN, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java
index 35e8137908..ce55a2127c 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.oauth2.Scope;
 import io.gravitee.am.repository.management.api.ScopeRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.ScopeMongo;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
@@ -51,8 +50,8 @@ public class MongoScopeRepository extends AbstractManagementMongoRepository impl
     @PostConstruct
     public void init() {
         scopesCollection = mongoOperations.getCollection("scopes", ScopeMongo.class);
-        scopesCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        scopesCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_KEY, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(scopesCollection, new Document(FIELD_DOMAIN, 1));
+        super.createIndex(scopesCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_KEY, 1));
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java
index f0d4afcd17..a3c0af8e76 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java
@@ -24,7 +24,6 @@ import io.gravitee.am.model.scim.Address;
 import io.gravitee.am.model.scim.Attribute;
 import io.gravitee.am.model.scim.Certificate;
 import io.gravitee.am.repository.management.api.UserRepository;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.management.internal.model.UserMongo;
 import io.gravitee.am.repository.mongodb.management.internal.model.scim.AddressMongo;
 import io.gravitee.am.repository.mongodb.management.internal.model.scim.AttributeMongo;
@@ -63,12 +62,12 @@ public class MongoUserRepository extends AbstractManagementMongoRepository imple
     @PostConstruct
     public void init() {
         usersCollection = mongoOperations.getCollection("users", UserMongo.class);
-        usersCollection.createIndex(new Document(FIELD_DOMAIN, 1)).subscribe(new LoggableIndexSubscriber());
-        usersCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_EMAIL, 1)).subscribe(new LoggableIndexSubscriber());
-        usersCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_USERNAME, 1)).subscribe(new LoggableIndexSubscriber());
-        usersCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_EXTERNAL_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        usersCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_USERNAME, 1).append(FIELD_SOURCE, 1)).subscribe(new LoggableIndexSubscriber());
-        usersCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_EXTERNAL_ID, 1).append(FIELD_SOURCE, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(usersCollection, new Document(FIELD_DOMAIN, 1));
+        super.createIndex(usersCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_EMAIL, 1));
+        super.createIndex(usersCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_USERNAME, 1));
+        super.createIndex(usersCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_EXTERNAL_ID, 1));
+        super.createIndex(usersCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_USERNAME, 1).append(FIELD_SOURCE, 1));
+        super.createIndex(usersCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_EXTERNAL_ID, 1).append(FIELD_SOURCE, 1));
     }
 
     @Override
@@ -84,7 +83,7 @@ public class MongoUserRepository extends AbstractManagementMongoRepository imple
     }
 
     @Override
-    public Single<Page<User>> search(String domain, String query, int limit) {
+    public Single<Page<User>> search(String domain, String query, int page, int size) {
         // currently search on username field
         Bson searchQuery = new BasicDBObject(FIELD_USERNAME, query);
         // if query contains wildcard, use the regex query
@@ -99,7 +98,7 @@ public class MongoUserRepository extends AbstractManagementMongoRepository imple
                 searchQuery);
 
         Single<Long> countOperation = Observable.fromPublisher(usersCollection.countDocuments(mongoQuery)).first(0l);
-        Single<Set<User>> usersOperation = Observable.fromPublisher(usersCollection.find(mongoQuery).limit(limit)).map(this::convert).collect(LinkedHashSet::new, Set::add);
+        Single<Set<User>> usersOperation = Observable.fromPublisher(usersCollection.find(mongoQuery).skip(size * page).limit(size)).map(this::convert).collect(LinkedHashSet::new, Set::add);
         return Single.zip(countOperation, usersOperation, (count, users) -> new Page<>(users, 0, count));
     }
 
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/AbstractOAuth2MongoRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/AbstractOAuth2MongoRepository.java
index 28b2f8ce21..aa60f3a8d3 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/AbstractOAuth2MongoRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/AbstractOAuth2MongoRepository.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.repository.mongodb.oauth2;
 
 import com.mongodb.reactivestreams.client.MongoDatabase;
+import io.gravitee.am.repository.mongodb.common.AbstractMongoRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
@@ -23,7 +24,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
  */
-public abstract class AbstractOAuth2MongoRepository {
+public abstract class AbstractOAuth2MongoRepository extends AbstractMongoRepository {
 
     @Autowired
     @Qualifier("oauth2MongoTemplate")
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAccessTokenRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAccessTokenRepository.java
index 9737dc141b..fb583dc879 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAccessTokenRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAccessTokenRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.client.model.IndexOptions;
 import com.mongodb.client.model.InsertOneModel;
 import com.mongodb.client.model.WriteModel;
 import com.mongodb.reactivestreams.client.MongoCollection;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.oauth2.internal.model.AccessTokenMongo;
 import io.gravitee.am.repository.oauth2.api.AccessTokenRepository;
 import io.gravitee.am.repository.oauth2.model.AccessToken;
@@ -56,16 +55,16 @@ public class MongoAccessTokenRepository extends AbstractOAuth2MongoRepository im
         accessTokenCollection = mongoOperations.getCollection("access_tokens", AccessTokenMongo.class);
 
         // one field index
-        accessTokenCollection.createIndex(new Document(FIELD_TOKEN, 1)).subscribe(new LoggableIndexSubscriber());
-        accessTokenCollection.createIndex(new Document(FIELD_CLIENT_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        accessTokenCollection.createIndex(new Document(FIELD_AUTHORIZATION_CODE, 1)).subscribe(new LoggableIndexSubscriber());
-        accessTokenCollection.createIndex(new Document(FIELD_SUBJECT, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(accessTokenCollection, new Document(FIELD_TOKEN, 1));
+        super.createIndex(accessTokenCollection, new Document(FIELD_CLIENT_ID, 1));
+        super.createIndex(accessTokenCollection, new Document(FIELD_AUTHORIZATION_CODE, 1));
+        super.createIndex(accessTokenCollection, new Document(FIELD_SUBJECT, 1));
 
         // two fields index
-        accessTokenCollection.createIndex(new Document(FIELD_CLIENT_ID, 1).append(FIELD_SUBJECT, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(accessTokenCollection, new Document(FIELD_CLIENT_ID, 1).append(FIELD_SUBJECT, 1));
 
         // expire after index
-        accessTokenCollection.createIndex(new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(accessTokenCollection, new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS));
     }
 
     private Maybe<AccessToken> findById(String id) {
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java
index fdd7d4344f..00e18a7637 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java
@@ -18,7 +18,6 @@ package io.gravitee.am.repository.mongodb.oauth2;
 import com.mongodb.client.model.IndexOptions;
 import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.oauth2.internal.model.AuthorizationCodeMongo;
 import io.gravitee.am.repository.oauth2.api.AuthorizationCodeRepository;
 import io.gravitee.am.repository.oauth2.model.AuthorizationCode;
@@ -52,9 +51,9 @@ public class MongoAuthorizationCodeRepository extends AbstractOAuth2MongoReposit
     @PostConstruct
     public void init() {
         authorizationCodeCollection = mongoOperations.getCollection("authorization_codes", AuthorizationCodeMongo.class);
-        authorizationCodeCollection.createIndex(new Document(FIELD_CODE, 1)).subscribe(new LoggableIndexSubscriber());
-        authorizationCodeCollection.createIndex(new Document(FIELD_TRANSACTION_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        authorizationCodeCollection.createIndex(new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0l, TimeUnit.SECONDS)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(authorizationCodeCollection, new Document(FIELD_CODE, 1));
+        super.createIndex(authorizationCodeCollection, new Document(FIELD_TRANSACTION_ID, 1));
+        super.createIndex(authorizationCodeCollection, new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0l, TimeUnit.SECONDS));
     }
 
     private Maybe<AuthorizationCode> findById(String id) {
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoRefreshTokenRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoRefreshTokenRepository.java
index 14dd67e0a8..5c425a428c 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoRefreshTokenRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoRefreshTokenRepository.java
@@ -20,7 +20,6 @@ import com.mongodb.client.model.InsertOneModel;
 import com.mongodb.client.model.WriteModel;
 import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.oauth2.internal.model.RefreshTokenMongo;
 import io.gravitee.am.repository.oauth2.api.RefreshTokenRepository;
 import io.gravitee.am.repository.oauth2.model.RefreshToken;
@@ -51,9 +50,9 @@ public class MongoRefreshTokenRepository extends AbstractOAuth2MongoRepository i
     @PostConstruct
     public void init() {
         refreshTokenCollection = mongoOperations.getCollection("refresh_tokens", RefreshTokenMongo.class);
-        refreshTokenCollection.createIndex(new Document(FIELD_TOKEN, 1)).subscribe(new LoggableIndexSubscriber());
-        refreshTokenCollection.createIndex(new Document(FIELD_SUBJECT, 1)).subscribe(new LoggableIndexSubscriber());
-        refreshTokenCollection.createIndex(new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(refreshTokenCollection, new Document(FIELD_TOKEN, 1));
+        super.createIndex(refreshTokenCollection, new Document(FIELD_SUBJECT, 1));
+        super.createIndex(refreshTokenCollection, new Document(FIELD_RESET_TIME, 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS));
     }
 
     private Maybe<RefreshToken> findById(String id) {
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoScopeApprovalRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoScopeApprovalRepository.java
index ac7ca49561..0279f86448 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoScopeApprovalRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoScopeApprovalRepository.java
@@ -19,7 +19,6 @@ import com.mongodb.client.model.IndexOptions;
 import com.mongodb.reactivestreams.client.MongoCollection;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.oauth2.ScopeApproval;
-import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.oauth2.internal.model.ScopeApprovalMongo;
 import io.gravitee.am.repository.oauth2.api.ScopeApprovalRepository;
 import io.reactivex.Completable;
@@ -59,11 +58,11 @@ public class MongoScopeApprovalRepository extends AbstractOAuth2MongoRepository
     @PostConstruct
     public void init() {
         scopeApprovalsCollection = mongoOperations.getCollection("scope_approvals", ScopeApprovalMongo.class);
-        scopeApprovalsCollection.createIndex(new Document(FIELD_EXPIRES_AT, 1),  new IndexOptions().expireAfter(0l, TimeUnit.SECONDS)).subscribe(new LoggableIndexSubscriber());
-        scopeApprovalsCollection.createIndex(new Document(FIELD_TRANSACTION_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        scopeApprovalsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_USER_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        scopeApprovalsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT_ID, 1).append(FIELD_USER_ID, 1)).subscribe(new LoggableIndexSubscriber());
-        scopeApprovalsCollection.createIndex(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT_ID, 1).append(FIELD_USER_ID, 1).append(FIELD_SCOPE, 1)).subscribe(new LoggableIndexSubscriber());
+        super.createIndex(scopeApprovalsCollection, new Document(FIELD_EXPIRES_AT, 1),  new IndexOptions().expireAfter(0l, TimeUnit.SECONDS));
+        super.createIndex(scopeApprovalsCollection, new Document(FIELD_TRANSACTION_ID, 1));
+        super.createIndex(scopeApprovalsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_USER_ID, 1));
+        super.createIndex(scopeApprovalsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT_ID, 1).append(FIELD_USER_ID, 1));
+        super.createIndex(scopeApprovalsCollection, new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT_ID, 1).append(FIELD_USER_ID, 1).append(FIELD_SCOPE, 1));
     }
 
     @Override
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java
index 7aa2019ed4..dab97741ed 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java
@@ -37,7 +37,7 @@ public interface UserService {
 
     Single<Page<User>> findByDomain(String domain, int page, int size);
 
-    Single<Page<User>> search(String domain, String query, int limit);
+    Single<Page<User>> search(String domain, String query, int page, int size);
 
     Single<List<User>> findByIdIn(List<String> ids);
 
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java
index 22403134d9..db692522c0 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java
@@ -80,9 +80,9 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
-    public Single<Page<User>> search(String domain, String query, int limit) {
+    public Single<Page<User>> search(String domain, String query, int page, int size) {
         LOGGER.debug("Search users for domain {} with query {}", domain, query);
-        return userRepository.search(domain, query, limit)
+        return userRepository.search(domain, query, page, size)
                 .onErrorResumeNext(ex -> {
                     LOGGER.error("An error occurs while trying to search users for domain {} and query {}", domain, query, ex);
                     return Single.error(new TechnicalManagementException(String.format("An error occurs while trying to find users for domain %s and query %s", domain, query), ex));
diff --git a/postman/collections/graviteeio-am-api-management-collection.json b/postman/collections/graviteeio-am-api-management-collection.json
new file mode 100644
index 0000000000..2e41d7e70e
--- /dev/null
+++ b/postman/collections/graviteeio-am-api-management-collection.json
@@ -0,0 +1,361 @@
+{
+	"info": {
+		"_postman_id": "c55b4035-63d4-4790-9ee3-ba5306246751",
+		"name": "Gravitee.io - AM - API Management",
+		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+	},
+	"item": [
+		{
+			"name": "Prepare",
+			"item": [
+				{
+					"name": "Generate admin token",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "571e9b59-b9e7-452c-9469-9786ded290a6",
+								"exec": [
+									"",
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"var token = JSON.parse(responseBody);",
+									"pm.environment.set('token', token.access_token);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Authorization",
+								"value": "Basic YWRtaW46YWRtaW5hZG1pbg=="
+							}
+						],
+						"body": {
+							"mode": "urlencoded",
+							"urlencoded": [
+								{
+									"key": "grant_type",
+									"value": "password",
+									"type": "text"
+								},
+								{
+									"key": "username",
+									"value": "admin",
+									"type": "text"
+								},
+								{
+									"key": "password",
+									"value": "adminadmin",
+									"type": "text"
+								}
+							]
+						},
+						"url": {
+							"raw": "{{management_url}}/admin/token",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"admin",
+								"token"
+							]
+						}
+					},
+					"response": []
+				}
+			],
+			"protocolProfileBehavior": {}
+		},
+		{
+			"name": "Login",
+			"item": [
+				{
+					"name": "Get admin configuration",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "86a05f9d-bc53-4105-b7b5-7158e9ac9339",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"var jsonData = pm.response.json();",
+									"pm.environment.set('currentIdp', jsonData.identities[0]);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}",
+								"type": "text"
+							}
+						],
+						"url": {
+							"raw": "{{management_url}}/management/domains/admin",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"admin"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create new identity provider",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "d156cb2e-9446-4a09-ae08-fb1717c61f7a",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"var jsonData = pm.response.json();",
+									"pm.environment.set('newIdp', jsonData.id);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"value": "application/json",
+								"type": "text"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}",
+								"type": "text"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n\t\"external\":false,\n\t\"type\":\"inline-am-idp\",\n\t\"configuration\":\"{\\\"users\\\":[{\\\"firstname\\\":\\\"test\\\",\\\"lastname\\\":\\\"test\\\",\\\"username\\\":\\\"test\\\",\\\"email\\\":\\\"test@test.com\\\",\\\"password\\\":\\\"test\\\"}]}\",\n\t\"name\":\"Inline Users 2\"\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/admin/identities",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"admin",
+								"identities"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Update admin configuration",
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"value": "application/json",
+								"type": "text"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}",
+								"type": "text"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\"identities\":[\"{{currentIdp}}\",\"{{newIdp}}\"]}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/admin",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"admin"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Generate admin token",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "571e9b59-b9e7-452c-9469-9786ded290a6",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Authorization",
+								"value": "Basic YWRtaW46YWRtaW5hZG1pbg=="
+							}
+						],
+						"body": {
+							"mode": "urlencoded",
+							"urlencoded": [
+								{
+									"key": "grant_type",
+									"value": "password",
+									"type": "text"
+								},
+								{
+									"key": "username",
+									"value": "test",
+									"type": "text"
+								},
+								{
+									"key": "password",
+									"value": "test",
+									"type": "text"
+								}
+							]
+						},
+						"url": {
+							"raw": "{{management_url}}/admin/token",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"admin",
+								"token"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Reset admin configuration",
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\"identities\":[\"{{currentIdp}}\"]}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/admin",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"admin"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Delete new identity provider",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "d156cb2e-9446-4a09-ae08-fb1717c61f7a",
+								"exec": [
+									"pm.test(\"Status code is 204\", function () {",
+									"    pm.response.to.have.status(204);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"url": {
+							"raw": "{{management_url}}/management/domains/admin/identities/{{newIdp}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"admin",
+								"identities",
+								"{{newIdp}}"
+							]
+						}
+					},
+					"response": []
+				}
+			],
+			"protocolProfileBehavior": {}
+		}
+	],
+	"protocolProfileBehavior": {}
+}
\ No newline at end of file
diff --git a/postman/collections/graviteeio-am-oauth2-collection.json b/postman/collections/graviteeio-am-oauth2-collection.json
index fc70e02f63..6cc7251ac6 100644
--- a/postman/collections/graviteeio-am-oauth2-collection.json
+++ b/postman/collections/graviteeio-am-oauth2-collection.json
@@ -1,6 +1,6 @@
 {
 	"info": {
-		"_postman_id": "363baad7-40ce-452c-a5e1-71e611a8b857",
+		"_postman_id": "3623be71-518f-441d-99d6-9c1564f165ca",
 		"name": "Gravitee.io - AM - Oauth2",
 		"description": "Test Oauth2 (RFC 6749) specifications",
 		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
@@ -1043,14 +1043,8 @@
 									"script": {
 										"id": "1ae3c79d-d2e7-49ab-bd20-70629648df50",
 										"exec": [
-											"pm.test(\"Status code is 400\", function () {",
-											"    pm.response.to.have.status(400);",
-											"});",
-											"",
-											"pm.test(\"Invalid scope\", function () {",
-											"    pm.response.to.be.header('Content-Type', 'application/json');",
-											"    var body = pm.response.json();",
-											"    pm.expect(body.error).to.eql('invalid_scope');",
+											"pm.test(\"Status code is 200\", function () {",
+											"    pm.response.to.have.status(200);",
 											"});"
 										],
 										"type": "text/javascript"
@@ -1859,14 +1853,8 @@
 									"script": {
 										"id": "e9d398f2-b014-4b0a-9c2d-e116fc99298d",
 										"exec": [
-											"pm.test(\"Status code is 400\", function () {",
-											"    pm.response.to.have.status(400);",
-											"});",
-											"",
-											"pm.test(\"Invalid grant\", function () {",
-											"    pm.response.to.be.header('Content-Type', 'application/json');",
-											"    var body = pm.response.json();",
-											"    pm.expect(body.error).to.eql('invalid_scope');",
+											"pm.test(\"Status code is 200\", function () {",
+											"    pm.response.to.have.status(200);",
 											"});"
 										],
 										"type": "text/javascript"
@@ -13107,4 +13095,4 @@
 		}
 	],
 	"protocolProfileBehavior": {}
-}
+}
\ No newline at end of file


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/AuthorizationEndpointTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/AuthorizationEndpointTest.java
index dd0700924d..00824236b0 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/AuthorizationEndpointTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/AuthorizationEndpointTest.java
@@ -157,13 +157,28 @@ public class AuthorizationEndpointTest extends RxWebTestBase {
     }
 
     @Test
-    public void shouldNotInvokeAuthorizationEndpoint_emptyScope() throws Exception {
+    public void shouldInvokeAuthorizationEndpoint_emptyScope() 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(ResponseType.CODE);
+        authorizationRequest.setRedirectUri("http://localhost:9999/callback");
+
+        AuthorizationResponse authorizationResponse = new AuthorizationCodeResponse();
+        authorizationResponse.setRedirectUri(authorizationRequest.getRedirectUri());
+        ((AuthorizationCodeResponse) authorizationResponse).setCode("test-code");
+
+        router.route().order(-1).handler(routingContext -> {
+            routingContext.setUser(new User(new io.gravitee.am.gateway.handler.common.vertx.web.auth.user.User(new io.gravitee.am.model.User())));
+            routingContext.next();
+        });
+
         when(clientSyncService.findByClientId("client-id")).thenReturn(Maybe.just(client));
+        when(flow.run(any(), any(), any())).thenReturn(Single.just(authorizationResponse));
 
         router.route().order(-1).handler(routingContext -> {
             routingContext.put(CLIENT_CONTEXT_KEY, client);
@@ -176,7 +191,7 @@ public class AuthorizationEndpointTest extends RxWebTestBase {
                 resp -> {
                     String location = resp.headers().get("location");
                     assertNotNull(location);
-                    assertEquals("http://localhost:9999/callback?error=invalid_scope&error_description=Invalid+parameter%253A+scope+must+not+be+empty", location);
+                    assertEquals("http://localhost:9999/callback?code=test-code", location);
                 },
                 HttpStatusCode.FOUND_302, "Found", null);
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/TokenEndpointTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/TokenEndpointTest.java
index f1666e330c..e7b7774ea2 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/TokenEndpointTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/resources/endpoint/TokenEndpointTest.java
@@ -73,10 +73,10 @@ public class TokenEndpointTest extends RxWebTestBase {
     }
 
     @Test
-    public void shouldNotInvokeTokenEndpoint_emptyScope() throws Exception {
+    public void shouldInvokeTokenEndpoint_emptyScope() throws Exception {
         testRequest(
                 HttpMethod.POST, "/oauth/token?grant_type=client_credentials&scope=",
-                HttpStatusCode.BAD_REQUEST_400, "Bad Request");
+                HttpStatusCode.UNAUTHORIZED_401, "Unauthorized");
     }
 
     @Test
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/IntrospectionServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/IntrospectionServiceTest.java
new file mode 100644
index 0000000000..d441d7995a
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/service/introspection/IntrospectionServiceTest.java
@@ -0,0 +1,84 @@
+/**
+ * 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.oauth2.service.introspection;
+
+import io.gravitee.am.gateway.handler.oauth2.service.introspection.impl.IntrospectionServiceImpl;
+import io.gravitee.am.gateway.handler.oauth2.service.token.TokenService;
+import io.gravitee.am.gateway.handler.oauth2.service.token.impl.AccessToken;
+import io.gravitee.am.model.User;
+import io.gravitee.am.service.UserService;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class IntrospectionServiceTest {
+
+    @InjectMocks
+    private IntrospectionService introspectionService = new IntrospectionServiceImpl();
+
+    @Mock
+    private TokenService tokenService;
+
+    @Mock
+    private UserService userService;
+
+    @Test
+    public void shouldSearchForAUser() {
+        final String token = "token";
+        AccessToken accessToken = new AccessToken(token);
+        accessToken.setSubject("user");
+        accessToken.setClientId("client-id");
+        when(tokenService.introspect("token")).thenReturn(Single.just(accessToken));
+        when(userService.findById("user")).thenReturn(Maybe.just(new User()));
+
+        IntrospectionRequest introspectionRequest = new IntrospectionRequest(token);
+        TestObserver<IntrospectionResponse> testObserver = introspectionService.introspect(introspectionRequest).test();
+
+        testObserver.awaitTerminalEvent();
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(userService, times(1)).findById("user");
+    }
+
+    @Test
+    public void shouldNotSearchForAUser_clientCredentials() {
+        final String token = "token";
+        AccessToken accessToken = new AccessToken(token);
+        accessToken.setSubject("client-id");
+        accessToken.setClientId("client-id");
+        when(tokenService.introspect("token")).thenReturn(Single.just(accessToken));
+
+        IntrospectionRequest introspectionRequest = new IntrospectionRequest(token);
+        TestObserver<IntrospectionResponse> testObserver = introspectionService.introspect(introspectionRequest).test();
+
+        testObserver.awaitTerminalEvent();
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(userService, never()).findById(anyString());
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/test/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProviderTest.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/test/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProviderTest.java
new file mode 100644
index 0000000000..fd32a58e8e
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/src/test/java/io/gravitee/am/management/handlers/admin/provider/security/DomainBasedAuthenticationProviderTest.java
@@ -0,0 +1,122 @@
+/**
+ * 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.management.handlers.admin.provider.security;
+
+import io.gravitee.am.common.exception.authentication.BadCredentialsException;
+import io.gravitee.am.identityprovider.api.AuthenticationProvider;
+import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.management.handlers.admin.security.IdentityProviderManager;
+import io.gravitee.am.model.Domain;
+import io.reactivex.Maybe;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DomainBasedAuthenticationProviderTest {
+
+    @InjectMocks
+    private DomainBasedAuthenticationProvider domainBasedAuthenticationProvider =  new DomainBasedAuthenticationProvider();
+
+    @Mock
+    private Domain domain;
+
+    @Mock
+    private IdentityProviderManager identityProviderManager;
+
+    @Before
+    public void init() {
+        when(domain.getIdentities()).thenReturn(new LinkedHashSet<>(Arrays.asList("idp1", "idp2")));
+    }
+
+    @Test
+    public void shouldAuthenticate_firstAuthProvider() {
+        AuthenticationProvider authenticationProvider = mock(AuthenticationProvider.class);
+        when(authenticationProvider.loadUserByUsername(any(io.gravitee.am.identityprovider.api.Authentication.class))).thenReturn(Maybe.just(new DefaultUser("username")));
+        when(identityProviderManager.get("idp1")).thenReturn(authenticationProvider);
+
+        Authentication authentication = domainBasedAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken("username", "password"));
+
+        Assert.assertNotNull(authentication);
+        verify(identityProviderManager, times(1)).get("idp1");
+        verify(identityProviderManager, never()).get("idp2");
+    }
+
+    @Test
+    public void shouldAuthenticate_secondAuthProvider() {
+        AuthenticationProvider authenticationProvider = mock(AuthenticationProvider.class);
+        when(authenticationProvider.loadUserByUsername(any(io.gravitee.am.identityprovider.api.Authentication.class))).thenReturn(Maybe.error(new BadCredentialsException()));
+        AuthenticationProvider authenticationProvider2 = mock(AuthenticationProvider.class);
+        when(authenticationProvider2.loadUserByUsername(any(io.gravitee.am.identityprovider.api.Authentication.class))).thenReturn(Maybe.just(new DefaultUser("username")));
+        when(identityProviderManager.get("idp1")).thenReturn(authenticationProvider);
+        when(identityProviderManager.get("idp2")).thenReturn(authenticationProvider2);
+
+        Authentication authentication = domainBasedAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken("username", "password"));
+
+        Assert.assertNotNull(authentication);
+        verify(identityProviderManager, times(1)).get("idp1");
+        verify(identityProviderManager, times(1)).get("idp2");
+
+    }
+
+    @Test
+    public void shouldAuthenticate_secondAuthProvider_firstNull() {
+        AuthenticationProvider authenticationProvider2 = mock(AuthenticationProvider.class);
+        when(authenticationProvider2.loadUserByUsername(any(io.gravitee.am.identityprovider.api.Authentication.class))).thenReturn(Maybe.just(new DefaultUser("username")));
+        when(identityProviderManager.get("idp1")).thenReturn(null);
+        when(identityProviderManager.get("idp2")).thenReturn(authenticationProvider2);
+
+        Authentication authentication = domainBasedAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken("username", "password"));
+
+        Assert.assertNotNull(authentication);
+        verify(identityProviderManager, times(1)).get("idp1");
+        verify(identityProviderManager, times(1)).get("idp2");
+
+    }
+
+    @Test(expected = org.springframework.security.authentication.BadCredentialsException.class)
+    public void shouldNotAuthenticate_wrongCredentials() {
+        AuthenticationProvider authenticationProvider = mock(AuthenticationProvider.class);
+        when(authenticationProvider.loadUserByUsername(any(io.gravitee.am.identityprovider.api.Authentication.class))).thenReturn(Maybe.error(new BadCredentialsException()));
+        AuthenticationProvider authenticationProvider2 = mock(AuthenticationProvider.class);
+        when(authenticationProvider2.loadUserByUsername(any(io.gravitee.am.identityprovider.api.Authentication.class))).thenReturn(Maybe.error(new BadCredentialsException()));
+        when(identityProviderManager.get("idp1")).thenReturn(authenticationProvider);
+        when(identityProviderManager.get("idp2")).thenReturn(authenticationProvider2);
+
+        Authentication authentication = domainBasedAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken("username", "password"));
+
+        Assert.assertNull(authentication);
+        verify(identityProviderManager, times(1)).get("idp1");
+        verify(identityProviderManager, times(1)).get("idp2");
+
+    }
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEventRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEventRepositoryTest.java
index 1b20ed188c..4bc1e0d2b1 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEventRepositoryTest.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEventRepositoryTest.java
@@ -19,12 +19,15 @@ import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.common.event.Type;
 import io.gravitee.am.repository.exceptions.TechnicalException;
 import io.gravitee.am.repository.management.api.EventRepository;
+import io.reactivex.Single;
 import io.reactivex.observers.TestObserver;
+import io.reactivex.schedulers.Schedulers;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoUserRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoUserRepositoryTest.java
index eb2b6695d3..2a5a5ffa38 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoUserRepositoryTest.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoUserRepositoryTest.java
@@ -24,6 +24,7 @@ import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Set;
 
 /**
@@ -148,7 +149,7 @@ public class MongoUserRepositoryTest extends AbstractManagementRepositoryTest {
         userRepository.create(user2).blockingGet();
 
         // fetch user
-        TestObserver<Page<User>> testObserver = userRepository.search(domain, "testUsername", 10).test();
+        TestObserver<Page<User>> testObserver = userRepository.search(domain, "testUsername", 0, 10).test();
         testObserver.awaitTerminalEvent();
 
         testObserver.assertComplete();
@@ -173,12 +174,53 @@ public class MongoUserRepositoryTest extends AbstractManagementRepositoryTest {
         userRepository.create(user2).blockingGet();
 
         // fetch user
-        TestObserver<Page<User>> testObserver = userRepository.search(domain, "testUsername*", 10).test();
+        TestObserver<Page<User>> testObserver = userRepository.search(domain, "testUsername*", 0, 10).test();
         testObserver.awaitTerminalEvent();
 
         testObserver.assertComplete();
         testObserver.assertNoErrors();
         testObserver.assertValue(users -> users.getData().size() == 2);
     }
+    
+    @Test
+    public void testSearch_paged() {
+        final String domain = "domain";
+        // create user
+        User user1 = new User();
+        user1.setDomain(domain);
+        user1.setUsername("testUsername1");
+        userRepository.create(user1).blockingGet();
+
+        User user2 = new User();
+        user2.setDomain(domain);
+        user2.setUsername("testUsername2");
+        userRepository.create(user2).blockingGet();
+        
+        User user3 = new User();
+        user3.setDomain(domain);
+        user3.setUsername("testUsername3");
+        userRepository.create(user3).blockingGet();
+
+        // fetch user (page 0)
+        TestObserver<Page<User>> testObserverP0 = userRepository.search(domain, "testUsername*", 0, 2).test();
+        testObserverP0.awaitTerminalEvent();
+        
+        testObserverP0.assertComplete();
+        testObserverP0.assertNoErrors();
+        testObserverP0.assertValue(users -> users.getData().size() == 2);
+        testObserverP0.assertValue(users -> {
+        	Iterator<User> it = users.getData().iterator();
+        	return it.next().getUsername().equals(user1.getUsername()) && it.next().getUsername().equals(user2.getUsername());
+        });
+        
+        // fetch user (page 1)
+        TestObserver<Page<User>> testObserverP1 = userRepository.search(domain, "testUsername*", 1, 2).test();
+        testObserverP1.awaitTerminalEvent();
+        
+        testObserverP1.assertComplete();
+        testObserverP1.assertNoErrors();
+        testObserverP1.assertValue(users -> users.getData().size() == 1);
+        testObserverP1.assertValue(users -> users.getData().iterator().next().getUsername().equals(user3.getUsername()));
+    }
 
 }