Repository: gravitee-io/graviteeio-access-management
Pull request #521 (2019-02-14 10:02:13)

feat(form): manage form templates for clients
closes gravitee-io/issues#1910


Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/email/impl/EmailManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/email/impl/EmailManagerImpl.java
index 81ebfa0fb7..06d88adc72 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/email/impl/EmailManagerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/email/impl/EmailManagerImpl.java
@@ -19,7 +19,7 @@ import freemarker.cache.TemplateLoader;
 import freemarker.template.Configuration;
 import io.gravitee.am.gateway.core.event.EmailEvent;
 import io.gravitee.am.gateway.handler.email.EmailManager;
-import io.gravitee.am.gateway.handler.vertx.view.DomainBasedEmailTemplateLoader;
+import io.gravitee.am.gateway.handler.vertx.view.freemarker.DomainBasedEmailTemplateLoader;
 import io.gravitee.am.model.Domain;
 import io.gravitee.am.model.Email;
 import io.gravitee.am.model.common.event.Payload;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/FormManager.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/FormManager.java
index d66faff980..eea669057f 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/FormManager.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/FormManager.java
@@ -22,4 +22,6 @@ import io.gravitee.common.service.Service;
  * @author GraviteeSource Team
  */
 public interface FormManager extends Service {
+
+    String TEMPLATE_NAME_SEPARATOR = "|";
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/impl/FormManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/impl/FormManagerImpl.java
index 15413ea1f9..897fdfc75c 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/impl/FormManagerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/form/impl/FormManagerImpl.java
@@ -17,7 +17,7 @@ package io.gravitee.am.gateway.handler.form.impl;
 
 import io.gravitee.am.gateway.core.event.FormEvent;
 import io.gravitee.am.gateway.handler.form.FormManager;
-import io.gravitee.am.gateway.handler.vertx.view.DomainBasedTemplateResolver;
+import io.gravitee.am.gateway.handler.vertx.view.thymeleaf.DomainBasedTemplateResolver;
 import io.gravitee.am.model.Domain;
 import io.gravitee.am.model.Form;
 import io.gravitee.am.model.common.event.Payload;
@@ -64,7 +64,7 @@ public class FormManagerImpl extends AbstractService implements FormManager, Ini
         formRepository.findByDomain(domain.getId())
                 .subscribe(
                         forms -> {
-                            updatePages(forms);
+                            updateForms(forms);
                             logger.info("Forms loaded for domain {}", domain.getName());
                         },
                         error -> logger.error("Unable to initialize forms for domain {}", domain.getName(), error));
@@ -93,38 +93,43 @@ public class FormManagerImpl extends AbstractService implements FormManager, Ini
         }
     }
 
-    private void updateForm(String pageId, FormEvent formEvent) {
+    private void updateForm(String formId, FormEvent formEvent) {
         final String eventType = formEvent.toString().toLowerCase();
-        logger.info("Domain {} has received {} form event for {}", domain.getName(), eventType, pageId);
-        formRepository.findById(pageId)
+        logger.info("Domain {} has received {} form event for {}", domain.getName(), eventType, formId);
+        formRepository.findById(formId)
                 .subscribe(
-                        page -> {
-                            // check if page has been disabled
-                            if (forms.containsKey(pageId) && !page.isEnabled()) {
-                                removeForm(pageId);
+                        form -> {
+                            // check if form has been disabled
+                            if (forms.containsKey(formId) && !form.isEnabled()) {
+                                removeForm(formId);
                             } else {
-                                updatePages(Collections.singletonList(page));
+                                updateForms(Collections.singletonList(form));
                             }
-                            logger.info("Page {} {}d for domain {}", pageId, eventType, domain.getName());
+                            logger.info("Form {} {}d for domain {}", formId, eventType, domain.getName());
                         },
-                        error -> logger.error("Unable to {} page for domain {}", eventType, domain.getName(), error),
-                        () -> logger.error("No page found with id {}", pageId));
+                        error -> logger.error("Unable to {} form for domain {}", eventType, domain.getName(), error),
+                        () -> logger.error("No form found with id {}", formId));
     }
 
     private void removeForm(String formId) {
         logger.info("Domain {} has received form event, delete form {}", domain.getName(), formId);
         Form deletedForm = forms.remove(formId);
-        ((DomainBasedTemplateResolver) templateResolver).removePage(deletedForm);
+        ((DomainBasedTemplateResolver) templateResolver).removeForm(getTemplateName(deletedForm));
     }
 
-    private void updatePages(List<Form> forms) {
+    private void updateForms(List<Form> forms) {
         forms
                 .stream()
                 .filter(Form::isEnabled)
                 .forEach(form -> {
                     this.forms.put(form.getId(), form);
-                    ((DomainBasedTemplateResolver) templateResolver).addPage(form);
-                    logger.info("Form {} loaded for domain {}", form.getTemplate(), domain.getName());
+                    ((DomainBasedTemplateResolver) templateResolver).addForm(getTemplateName(form), form.getContent());
+                    logger.info("Form {} loaded for domain {} " + (form.getClient() != null ? "and client {}" : ""), form.getTemplate(), domain.getName(), form.getClient());
                 });
     }
+
+    private String getTemplateName(Form form) {
+        return form.getTemplate()
+                + ((form.getClient() != null) ? TEMPLATE_NAME_SEPARATOR + form.getClient() : "");
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/ClientSyncService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/ClientSyncService.java
index 9161666949..1212647779 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/ClientSyncService.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/ClientSyncService.java
@@ -25,6 +25,8 @@ import io.reactivex.Maybe;
  */
 public interface ClientSyncService extends Service {
 
+    Maybe<Client> findById(String id);
+
     Maybe<Client> findByClientId(String clientId);
 
     Maybe<Client> findByDomainAndClientId(String domain, String clientId);
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/impl/ClientSyncServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/impl/ClientSyncServiceImpl.java
index 08e77ecf88..9b651ccfd3 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/impl/ClientSyncServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/client/impl/ClientSyncServiceImpl.java
@@ -58,6 +58,13 @@ public class ClientSyncServiceImpl extends AbstractService implements ClientSync
     @Autowired
     private EventManager eventManager;
 
+    @Override
+    public Maybe<Client> findById(String id) {
+        return Observable.fromIterable(domainsClients.get(domain.getId()))
+                .filter(client -> client.getId().equals(id))
+                .firstElement();
+    }
+
     @Override
     public Maybe<Client> findByClientId(String clientId) {
         return findByDomainAndClientId(domain.getId(), clientId);
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java
index f1f90f3961..f2095d44dd 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/discovery/impl/OpenIDDiscoveryServiceImpl.java
@@ -82,7 +82,7 @@ public class OpenIDDiscoveryServiceImpl implements OpenIDDiscoveryService {
         openIDProviderMetadata.setClaimsSupported(Stream.of(Scope.values()).map(Scope::getClaims).flatMap(Collection::stream).distinct().collect(Collectors.toList()));
         openIDProviderMetadata.setCodeChallengeMethodsSupported(Arrays.asList(CodeChallengeMethod.PLAIN, CodeChallengeMethod.S256));
         openIDProviderMetadata.setClaimsParameterSupported(true);
-        //openIDProviderMetadata.setSubjectTypesSupported(Arrays.asList(SubjectType.PUBLIC));
+        openIDProviderMetadata.setSubjectTypesSupported(Arrays.asList(SubjectType.PUBLIC));
 
         return openIDProviderMetadata;
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/UserService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/UserService.java
index 1853fcbf08..b28520b886 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/UserService.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/UserService.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.gateway.handler.user;
 
+import io.gravitee.am.gateway.handler.user.model.UserToken;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.reactivex.Completable;
@@ -27,7 +28,7 @@ import io.reactivex.Single;
  */
 public interface UserService {
 
-    Maybe<User> verifyToken(String token);
+    Maybe<UserToken> verifyToken(String token);
 
     Single<User> register(User user);
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java
index 699486b09c..457ec46d61 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java
@@ -25,7 +25,9 @@ import io.gravitee.am.gateway.handler.email.EmailManager;
 import io.gravitee.am.gateway.handler.email.EmailService;
 import io.gravitee.am.gateway.handler.jwt.JwtBuilder;
 import io.gravitee.am.gateway.handler.jwt.JwtParser;
+import io.gravitee.am.gateway.handler.oauth2.client.ClientSyncService;
 import io.gravitee.am.gateway.handler.user.UserService;
+import io.gravitee.am.gateway.handler.user.model.UserToken;
 import io.gravitee.am.identityprovider.api.DefaultUser;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.Domain;
@@ -37,6 +39,7 @@ import io.gravitee.am.service.exception.UserNotFoundException;
 import io.gravitee.am.service.exception.UserProviderNotFoundException;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
+import io.reactivex.MaybeSource;
 import io.reactivex.Single;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -44,6 +47,7 @@ import org.springframework.beans.factory.annotation.Value;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -83,10 +87,13 @@ public class UserServiceImpl implements UserService {
     @Autowired
     private EmailManager emailManager;
 
+    @Autowired
+    private ClientSyncService clientSyncService;
+
     @Override
-    public Maybe<User> verifyToken(String token) {
+    public Maybe<UserToken> verifyToken(String token) {
         return Maybe.fromCallable(() -> jwtParser.parse(token))
-                .flatMap(jwt -> userRepository.findById(jwt.getSub()));
+                .flatMap(jwt -> userRepository.findById(jwt.getSub()).zipWith(clientSource(jwt.getAud()), (user, optionalClient) -> new UserToken(user, optionalClient.orElse(null))));
     }
 
     @Override
@@ -169,12 +176,12 @@ public class UserServiceImpl implements UserService {
 
     private void completeForgotPassword(User user, Client client) {
         io.gravitee.am.model.Email email = emailManager.getEmail(getTemplateName(client), resetPasswordSubject, expireAfter);
-        Email email1 = convert(user, email, "/resetPassword", "resetPasswordUrl");
+        Email email1 = convert(user, client, email, "/resetPassword", "resetPasswordUrl");
         emailService.send(email1);
     }
 
-    private Email convert(User user, io.gravitee.am.model.Email email, String redirectUri, String redirectUriName) {
-        Map<String, Object> params = prepareEmail(user, email.getExpiresAfter(), redirectUri, redirectUriName);
+    private Email convert(User user, Client client, io.gravitee.am.model.Email email, String redirectUri, String redirectUriName) {
+        Map<String, Object> params = prepareEmail(user, client, email.getExpiresAfter(), redirectUri, redirectUriName);
         Email email1 = new EmailBuilder()
                 .to(user.getEmail())
                 .from(email.getFrom())
@@ -186,12 +193,13 @@ public class UserServiceImpl implements UserService {
         return email1;
     }
 
-    private Map<String, Object> prepareEmail(User user, int expiresAfter, String redirectUri, String redirectUriName) {
+    private Map<String, Object> prepareEmail(User user, Client client, int expiresAfter, String redirectUri, String redirectUriName) {
         // generate a JWT to store user's information and for security purpose
         final Map<String, Object> claims = new HashMap<>();
         claims.put(Claims.iat, new Date().getTime() / 1000);
         claims.put(Claims.exp, new Date(System.currentTimeMillis() + (expiresAfter * 1000)).getTime() / 1000);
         claims.put(Claims.sub, user.getId());
+        claims.put(Claims.aud, client.getId());
         claims.put(StandardClaims.EMAIL, user.getEmail());
         claims.put(StandardClaims.GIVEN_NAME, user.getFirstName());
         claims.put(StandardClaims.FAMILY_NAME, user.getLastName());
@@ -219,6 +227,15 @@ public class UserServiceImpl implements UserService {
                 + ((client != null) ? EmailManager.TEMPLATE_NAME_SEPARATOR +  client.getId() : "");
     }
 
+    private MaybeSource<Optional<Client>> clientSource(String audience) {
+        if (audience == null) {
+            return Maybe.just(Optional.empty());
+        }
+
+        return clientSyncService.findById(audience)
+                .map(client -> Optional.of(client))
+                .defaultIfEmpty(Optional.empty());
+    }
 
     private io.gravitee.am.identityprovider.api.User convert(User user) {
         DefaultUser idpUser = new DefaultUser(user.getUsername());
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/model/UserToken.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/model/UserToken.java
new file mode 100644
index 0000000000..c078c26bdd
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/model/UserToken.java
@@ -0,0 +1,42 @@
+/**
+ * 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.user.model;
+
+import io.gravitee.am.model.Client;
+import io.gravitee.am.model.User;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class UserToken {
+
+    private final User user;
+    private final Client client;
+
+    public UserToken(User user, Client client) {
+        this.user = user;
+        this.client = client;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public Client getClient() {
+        return client;
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
index ca53bd97b4..e77935556e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
@@ -186,6 +186,20 @@ public class VertxSecurityDomainHandler {
                 .handler(cookieHandler)
                 .handler(sessionHandler)
                 .handler(userSessionHandler);
+
+        // Registration confirmation endpoint
+        router
+                .route("/confirmRegistration")
+                .handler(cookieHandler)
+                .handler(sessionHandler)
+                .handler(userSessionHandler);
+
+        // Reset password endpoint
+        router
+                .route("/resetPassword")
+                .handler(cookieHandler)
+                .handler(sessionHandler)
+                .handler(userSessionHandler);
     }
 
     private void csrfHandler(Router router) {
@@ -194,6 +208,8 @@ public class VertxSecurityDomainHandler {
         csrfHandler.setCookiePath("/" + domain.getPath());
         io.gravitee.am.gateway.handler.vertx.handler.CSRFHandler csrfHandler1 = io.gravitee.am.gateway.handler.vertx.handler.CSRFHandler.create();
         router.route("/login").handler(csrfHandler).handler(csrfHandler1);
+        router.route("/resetPassword").handler(csrfHandler).handler(csrfHandler1);
+        router.route("/confirmRegistration").handler(csrfHandler).handler(csrfHandler1);
         router.route("/oauth/confirm_access").handler(csrfHandler).handler(csrfHandler1);
     }
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/ExceptionHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/ExceptionHandler.java
index f378682424..528b4dd084 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/ExceptionHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/ExceptionHandler.java
@@ -50,7 +50,7 @@ public class ExceptionHandler implements Handler<RoutingContext> {
                         .setStatusCode(oAuth2Exception.getHttpStatusCode())
                         .end(Json.encodePrettily(oAuth2ErrorResponse));
             } else {
-                logger.error(throwable.getMessage(), throwable);
+                logger.error("An exception occurs while handling incoming request", throwable);
                 if (routingContext.statusCode() != -1) {
                     routingContext
                             .response()
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/UserApprovalEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/UserApprovalEndpointHandler.java
index 1b34de9141..05e7255d1a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/UserApprovalEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/UserApprovalEndpointHandler.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.gateway.handler.vertx.handler.oauth2.endpoint.authorization;
 
+import io.gravitee.am.gateway.handler.form.FormManager;
 import io.gravitee.am.gateway.handler.oauth2.client.ClientSyncService;
 import io.gravitee.am.gateway.handler.oauth2.request.AuthorizationRequest;
 import io.gravitee.am.gateway.handler.oauth2.scope.ScopeService;
@@ -70,12 +71,14 @@ public class UserApprovalEndpointHandler implements Handler<RoutingContext>  {
 
                         requestedScopes.add(requestedScope);
                     }
+                    // Remove client_secret if present to prevent leaks
+                    client.setClientSecret(null);
                     return new ApprovalData(client, requestedScopes);
                 })
                 .subscribe(approvalData -> {
                         routingContext.put("client", approvalData.getClient());
                         routingContext.put("scopes", approvalData.getScopes());
-                        engine.render(routingContext, "oauth2_user_consent", res -> {
+                        engine.render(routingContext, getTemplateFileName(approvalData.getClient()), res -> {
                             if (res.succeeded()) {
                                 routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                                 routingContext.response().end(res.result());
@@ -91,6 +94,10 @@ public class UserApprovalEndpointHandler implements Handler<RoutingContext>  {
         this.clientSyncService = clientSyncService;
     }
 
+    private String getTemplateFileName(Client client) {
+        return "oauth2_user_consent" + (client != null ? FormManager.TEMPLATE_NAME_SEPARATOR + client.getId() : "");
+    }
+
     private class ApprovalData {
         private Client client;
         private Set<Scope> scopes;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/RootRouter.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/RootRouter.java
index 9678a8ac77..0011e3d392 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/RootRouter.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/RootRouter.java
@@ -119,6 +119,7 @@ public class RootRouter {
         // mount forgot/reset registration pages only if the option is enabled
         if (domain.getLoginSettings() != null && domain.getLoginSettings().isRegisterEnabled()) {
             router.route(HttpMethod.GET, "/register")
+                    .handler(clientRequestParseHandler)
                     .handler(registerHandler);
             router.route(HttpMethod.POST, "/register")
                     .handler(registerSubmissionRequestHandler)
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/login/LoginEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/login/LoginEndpointHandler.java
index d7ad539362..d6dc736345 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/login/LoginEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/login/LoginEndpointHandler.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.login;
 
 import io.gravitee.am.gateway.handler.auth.idp.IdentityProviderManager;
+import io.gravitee.am.gateway.handler.form.FormManager;
 import io.gravitee.am.gateway.handler.oauth2.exception.InvalidRequestException;
 import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
 import io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.ClientRequestParseHandler;
@@ -81,7 +82,7 @@ public class LoginEndpointHandler implements Handler<RoutingContext> {
 
         // no OAuth2/Social provider render login page
         if (oauth2Identities == null || oauth2Identities.isEmpty()) {
-            renderLoginPage(routingContext);
+            renderLoginPage(routingContext, client);
             return;
         }
 
@@ -100,11 +101,11 @@ public class LoginEndpointHandler implements Handler<RoutingContext> {
             routingContext.put(OAUTH2_AUTHORIZE_URL_CONTEXT_KEY, oAuth2ProviderData.stream().collect(Collectors.toMap(o -> o.getIdentityProvider().getId(), o -> o.getAuthorizeUrl())));
 
             // render login page
-            renderLoginPage(routingContext);
+            renderLoginPage(routingContext, client);
         });
     }
 
-    private void renderLoginPage(RoutingContext routingContext) {
+    private void renderLoginPage(RoutingContext routingContext, Client client) {
         // remove client context to avoid any leaks from custom login pages
         routingContext.remove(ClientRequestParseHandler.CLIENT_CONTEXT_KEY);
         // put domain in context data
@@ -123,7 +124,7 @@ public class LoginEndpointHandler implements Handler<RoutingContext> {
         routingContext.put(PARAM_CONTEXT_KEY, params);
 
         // render the login page
-        engine.render(routingContext, "login", res -> {
+        engine.render(routingContext, getTemplateFileName(client), res -> {
             if (res.succeeded()) {
                 routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                 routingContext.response().end(res.result());
@@ -179,6 +180,10 @@ public class LoginEndpointHandler implements Handler<RoutingContext> {
                 Collections.singletonMap("provider", identity));
     }
 
+    private String getTemplateFileName(Client client) {
+        return "login" + (client != null ? FormManager.TEMPLATE_NAME_SEPARATOR + client.getId() : "");
+    }
+
     private class OAuth2ProviderData {
         private IdentityProvider identityProvider;
         private String authorizeUrl;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/UserTokenRequestParseHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/UserTokenRequestParseHandler.java
index 6714d110f7..84c7b6bdb9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/UserTokenRequestParseHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/UserTokenRequestParseHandler.java
@@ -17,7 +17,7 @@ package io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.user;
 
 import io.gravitee.am.gateway.handler.oauth2.exception.InvalidTokenException;
 import io.gravitee.am.gateway.handler.user.UserService;
-import io.gravitee.am.model.User;
+import io.gravitee.am.gateway.handler.user.model.UserToken;
 import io.vertx.core.AsyncResult;
 import io.vertx.core.Future;
 import io.vertx.core.Handler;
@@ -80,16 +80,18 @@ public class UserTokenRequestParseHandler extends UserRequestHandler {
                 return;
             }
 
-            // put user in context
-            context.put("user", handler.result());
+            // put user and client in context
+            UserToken userToken = handler.result();
+            context.put("user", userToken.getUser());
+            context.put("client", userToken.getClient());
             context.next();
         });
     }
 
-    private void parseToken(String token, Handler<AsyncResult<User>> handler) {
+    private void parseToken(String token, Handler<AsyncResult<UserToken>> handler) {
         userService.verifyToken(token)
                 .subscribe(
-                        user -> handler.handle(Future.succeededFuture(user)),
+                        userToken -> handler.handle(Future.succeededFuture(userToken)),
                         error -> handler.handle(Future.failedFuture(error)),
                         () -> handler.handle(Future.failedFuture(new InvalidTokenException("The JWT token is invalid"))));
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ForgotPasswordEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ForgotPasswordEndpointHandler.java
index 8545bfe6f7..f7195d1a43 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ForgotPasswordEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ForgotPasswordEndpointHandler.java
@@ -15,7 +15,10 @@
  */
 package io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.user.password;
 
+import io.gravitee.am.gateway.handler.form.FormManager;
 import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
+import io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.ClientRequestParseHandler;
+import io.gravitee.am.model.Client;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.MediaType;
 import io.vertx.core.Handler;
@@ -50,6 +53,7 @@ public class ForgotPasswordEndpointHandler implements Handler<RoutingContext> {
         final String error = request.getParam(ERROR_PARAM);
         final String success = request.getParam(SUCCESS_PARAM);
         final String warning = request.getParam(WARNING_PARAM);
+        final Client client = routingContext.get(ClientRequestParseHandler.CLIENT_CONTEXT_KEY);
         // add query params to context
         routingContext.put(ERROR_PARAM, error);
         routingContext.put(SUCCESS_PARAM, success);
@@ -57,7 +61,7 @@ public class ForgotPasswordEndpointHandler implements Handler<RoutingContext> {
         routingContext.put(PARAM_CONTEXT_KEY, Collections.singletonMap(OAuth2Constants.CLIENT_ID, request.getParam(OAuth2Constants.CLIENT_ID)));
 
         // render the forgot password page
-        engine.render(routingContext, "forgot_password", res -> {
+        engine.render(routingContext, getTemplateFileName(client), res -> {
             if (res.succeeded()) {
                 routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                 routingContext.response().end(res.result());
@@ -67,4 +71,8 @@ public class ForgotPasswordEndpointHandler implements Handler<RoutingContext> {
             }
         });
     }
+
+    private String getTemplateFileName(Client client) {
+        return "forgot_password" + (client != null ? FormManager.TEMPLATE_NAME_SEPARATOR + client.getId(): "");
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ResetPasswordEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ResetPasswordEndpointHandler.java
index a603169b24..697b54ed58 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ResetPasswordEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/password/ResetPasswordEndpointHandler.java
@@ -15,6 +15,8 @@
  */
 package io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.user.password;
 
+import io.gravitee.am.gateway.handler.form.FormManager;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.MediaType;
@@ -56,8 +58,11 @@ public class ResetPasswordEndpointHandler implements Handler<RoutingContext> {
         User user = routingContext.get("user");
         routingContext.put("user", user);
 
+        // retrieve client (if exists)
+        Client client = routingContext.get("client");
+
         // render the reset password page
-        engine.render(routingContext, "reset_password", res -> {
+        engine.render(routingContext, getTemplateFileName(client), res -> {
             if (res.succeeded()) {
                 routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                 routingContext.response().end(res.result());
@@ -67,4 +72,8 @@ public class ResetPasswordEndpointHandler implements Handler<RoutingContext> {
             }
         });
     }
+
+    private String getTemplateFileName(Client client) {
+        return "reset_password" + (client != null ? FormManager.TEMPLATE_NAME_SEPARATOR + client.getId(): "");
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterConfirmationEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterConfirmationEndpointHandler.java
index 68478282f1..3bf1fd4da7 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterConfirmationEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterConfirmationEndpointHandler.java
@@ -15,7 +15,9 @@
  */
 package io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.user.register;
 
+import io.gravitee.am.gateway.handler.form.FormManager;
 import io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.user.UserRequestHandler;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.MediaType;
@@ -58,6 +60,9 @@ public class RegisterConfirmationEndpointHandler extends UserRequestHandler {
         User user = routingContext.get("user");
         routingContext.put("user", user);
 
+        // retrieve client (if exists)
+        Client client = routingContext.get("client");
+
         // check if user has already completed its registration
         if (user != null && user.isPreRegistration() && user.isRegistrationCompleted()) {
             redirectToPage(routingContext, Collections.singletonMap("error", "invalid_registration_context"));
@@ -65,7 +70,7 @@ public class RegisterConfirmationEndpointHandler extends UserRequestHandler {
         }
 
         // render the registration confirmation page
-        engine.render(routingContext, "registration_confirmation", res -> {
+        engine.render(routingContext, getTemplateFileName(client), res -> {
             if (res.succeeded()) {
                 routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                 routingContext.response().end(res.result());
@@ -75,4 +80,8 @@ public class RegisterConfirmationEndpointHandler extends UserRequestHandler {
             }
         });
     }
+
+    private String getTemplateFileName(Client client) {
+        return "registration_confirmation" + (client != null ? FormManager.TEMPLATE_NAME_SEPARATOR + client.getId() : "");
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterEndpointHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterEndpointHandler.java
index 13a9c0778c..e195b2a681 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterEndpointHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/endpoint/user/register/RegisterEndpointHandler.java
@@ -15,7 +15,10 @@
  */
 package io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.user.register;
 
+import io.gravitee.am.gateway.handler.form.FormManager;
 import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
+import io.gravitee.am.gateway.handler.vertx.handler.root.endpoint.ClientRequestParseHandler;
+import io.gravitee.am.model.Client;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.MediaType;
 import io.vertx.core.Handler;
@@ -53,6 +56,7 @@ public class RegisterEndpointHandler implements Handler<RoutingContext> {
         final String success = request.getParam(SUCCESS_PARAM);
         final String warning = request.getParam(WARNING_PARAM);
         final String token = request.getParam(TOKEN_PARAM);
+        final Client client = routingContext.get(ClientRequestParseHandler.CLIENT_CONTEXT_KEY);
         // add query params to context
         routingContext.put(ERROR_PARAM, error);
         routingContext.put(SUCCESS_PARAM, success);
@@ -65,7 +69,7 @@ public class RegisterEndpointHandler implements Handler<RoutingContext> {
         routingContext.put(PARAM_CONTEXT_KEY, params);
 
         // render the registration confirmation page
-        engine.render(routingContext, "registration", res -> {
+        engine.render(routingContext, getTemplateFileName(client), res -> {
             if (res.succeeded()) {
                 routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                 routingContext.response().end(res.result());
@@ -75,4 +79,8 @@ public class RegisterEndpointHandler implements Handler<RoutingContext> {
             }
         });
     }
+
+    private String getTemplateFileName(Client client) {
+        return "registration" + (client != null ? FormManager.TEMPLATE_NAME_SEPARATOR + client.getId() : "");
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/handler/ErrorHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/handler/ErrorHandler.java
index efd64da6ef..17493015eb 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/handler/ErrorHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/root/handler/ErrorHandler.java
@@ -58,7 +58,7 @@ public class ErrorHandler implements Handler<RoutingContext> {
                 OAuth2Exception oAuth2Exception = (OAuth2Exception) throwable;
                 handleException(routingContext, oAuth2Exception.getOAuth2ErrorCode(), oAuth2Exception.getMessage());
             } else {
-                logger.error(throwable.getMessage(), throwable);
+                logger.error("An exception occurs while handling incoming request", throwable);
                 if (routingContext.statusCode() != -1) {
                     routingContext
                             .response()
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/spring/SecurityDomainRouterConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/spring/SecurityDomainRouterConfiguration.java
index 5968d2f86f..6e850e03ca 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/spring/SecurityDomainRouterConfiguration.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/spring/SecurityDomainRouterConfiguration.java
@@ -21,8 +21,8 @@ import io.gravitee.am.gateway.handler.vertx.handler.oauth2.OAuth2Router;
 import io.gravitee.am.gateway.handler.vertx.handler.oidc.OIDCRouter;
 import io.gravitee.am.gateway.handler.vertx.handler.root.RootRouter;
 import io.gravitee.am.gateway.handler.vertx.handler.scim.SCIMRouter;
-import io.gravitee.am.gateway.handler.vertx.view.FreeMarkerConfiguration;
-import io.gravitee.am.gateway.handler.vertx.view.ThymeleafConfiguration;
+import io.gravitee.am.gateway.handler.vertx.view.freemarker.FreeMarkerConfiguration;
+import io.gravitee.am.gateway.handler.vertx.view.thymeleaf.ThymeleafConfiguration;
 import io.gravitee.am.service.spring.ServiceConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/DomainBasedEmailTemplateLoader.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/freemarker/DomainBasedEmailTemplateLoader.java
similarity index 93%
rename from gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/DomainBasedEmailTemplateLoader.java
rename to gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/freemarker/DomainBasedEmailTemplateLoader.java
index d2263c77f7..60e45975a5 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/DomainBasedEmailTemplateLoader.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/freemarker/DomainBasedEmailTemplateLoader.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.gravitee.am.gateway.handler.vertx.view;
+package io.gravitee.am.gateway.handler.vertx.view.freemarker;
 
 import freemarker.cache.StringTemplateLoader;
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/FreeMarkerConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/freemarker/FreeMarkerConfiguration.java
similarity index 97%
rename from gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/FreeMarkerConfiguration.java
rename to gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/freemarker/FreeMarkerConfiguration.java
index 1388a227df..d47459e6c5 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/FreeMarkerConfiguration.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/freemarker/FreeMarkerConfiguration.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.gravitee.am.gateway.handler.vertx.view;
+package io.gravitee.am.gateway.handler.vertx.view.freemarker;
 
 import freemarker.cache.FileTemplateLoader;
 import freemarker.cache.MultiTemplateLoader;
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/CustomClassLoaderTemplateResolver.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/CustomClassLoaderTemplateResolver.java
new file mode 100644
index 0000000000..f348bdc7d3
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/CustomClassLoaderTemplateResolver.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.gateway.handler.vertx.view.thymeleaf;
+
+import io.gravitee.am.gateway.handler.form.FormManager;
+import org.thymeleaf.IEngineConfiguration;
+import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
+import org.thymeleaf.templateresource.ITemplateResource;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class CustomClassLoaderTemplateResolver extends ClassLoaderTemplateResolver {
+
+    @Override
+    protected ITemplateResource computeTemplateResource(IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) {
+        if (resourceName.contains(FormManager.TEMPLATE_NAME_SEPARATOR)) {
+            resourceName = resourceName.split(Pattern.quote(FormManager.TEMPLATE_NAME_SEPARATOR))[0] + ".html";
+        }
+        return super.computeTemplateResource(configuration, ownerTemplate, template, resourceName, characterEncoding, templateResolutionAttributes);
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/DomainBasedTemplateResolver.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/DomainBasedTemplateResolver.java
similarity index 61%
rename from gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/DomainBasedTemplateResolver.java
rename to gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/DomainBasedTemplateResolver.java
index ff9a5c515b..c30e4a6e79 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/DomainBasedTemplateResolver.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/DomainBasedTemplateResolver.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.gravitee.am.gateway.handler.vertx.view;
+package io.gravitee.am.gateway.handler.vertx.view.thymeleaf;
 
-import io.gravitee.am.model.Form;
+import io.gravitee.am.gateway.handler.form.FormManager;
 import org.thymeleaf.IEngineConfiguration;
 import org.thymeleaf.TemplateEngine;
 import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
@@ -25,6 +25,7 @@ import org.thymeleaf.templateresource.StringTemplateResource;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Pattern;
 
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
@@ -33,26 +34,36 @@ import java.util.concurrent.ConcurrentMap;
  */
 public class DomainBasedTemplateResolver extends AbstractConfigurableTemplateResolver {
 
-    private ConcurrentMap<String, String> templates = new ConcurrentHashMap<>();
+    private ConcurrentMap<String, StringTemplateResource> templates = new ConcurrentHashMap<>();
 
     private TemplateEngine templateEngine;
 
     @Override
     protected ITemplateResource computeTemplateResource(IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) {
-        if (templates.containsKey(resourceName)) {
-            return new StringTemplateResource(templates.get(resourceName));
+        boolean templateFound = templates.containsKey(resourceName);
+        String[] templateParts = resourceName.split(Pattern.quote(FormManager.TEMPLATE_NAME_SEPARATOR));
+
+        // template not found for the client, try at domain level
+        if (!templateFound && templateParts.length == 2) {
+            resourceName = templateParts[0];
+            templateFound = templates.containsKey(resourceName);
+        }
+
+        if (templateFound) {
+            return templates.get(resourceName);
         }
+
         return null;
     }
 
-    public void addPage(Form page) {
-        templates.put(page.getTemplate(), page.getContent());
-        templateEngine.clearTemplateCacheFor(page.getTemplate());
+    public void addForm(String templateName, String templateContent) {
+        templates.put(templateName, new StringTemplateResource(templateContent));
+        templateEngine.getCacheManager().clearAllCaches();
     }
 
-    public void removePage(Form page) {
-        templates.remove(page.getTemplate());
-        templateEngine.clearTemplateCacheFor(page.getTemplate());
+    public void removeForm(String templateName) {
+        templates.remove(templateName);
+        templateEngine.getCacheManager().clearAllCaches();
     }
 
     public void setTemplateEngine(TemplateEngine templateEngine) {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/ThymeleafConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/ThymeleafConfiguration.java
similarity index 93%
rename from gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/ThymeleafConfiguration.java
rename to gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/ThymeleafConfiguration.java
index 123d7da3c8..2c582d390b 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/ThymeleafConfiguration.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/view/thymeleaf/ThymeleafConfiguration.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.gravitee.am.gateway.handler.vertx.view;
+package io.gravitee.am.gateway.handler.vertx.view.thymeleaf;
 
 import io.vertx.reactivex.ext.web.templ.ThymeleafTemplateEngine;
 import org.springframework.context.annotation.Bean;
@@ -50,7 +50,7 @@ public class ThymeleafConfiguration {
 
     }
     private ITemplateResolver defaultTemplateResolver() {
-        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
+        ClassLoaderTemplateResolver templateResolver = new CustomClassLoaderTemplateResolver();
         templateResolver.setPrefix("/webroot/views/");
         templateResolver.setSuffix(".html");
         templateResolver.setTemplateMode("HTML");
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/registration_confirmation.html b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/registration_confirmation.html
index 0413060ea2..fce7892ebc 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/registration_confirmation.html
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/registration_confirmation.html
@@ -32,6 +32,7 @@
                     <i id="changePasswordVisibility" class="mdl-textfield__icon material-icons">visibility</i>
                 </div>
 
+                <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
                 <input type="hidden" name="token" id="token" th:value="${token}"/>
                 <button type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--primary">Confirm registration</button>
             </form>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/reset_password.html b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/reset_password.html
index bf5db4f6fe..8a959ce0e5 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/reset_password.html
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/resources/webroot/views/reset_password.html
@@ -32,6 +32,7 @@
                     <i id="changePasswordVisibility" class="mdl-textfield__icon material-icons">visibility</i>
                 </div>
 
+                <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
                 <input type="hidden" name="token" id="token" th:value="${token}"/>
                 <button type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--primary">Reset password</button>
             </form>
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/ClientFormResource.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/ClientFormResource.java
new file mode 100644
index 0000000000..265339f629
--- /dev/null
+++ 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/ClientFormResource.java
@@ -0,0 +1,91 @@
+/**
+ * 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.model.Form;
+import io.gravitee.am.service.ClientService;
+import io.gravitee.am.service.DomainService;
+import io.gravitee.am.service.FormService;
+import io.gravitee.am.service.exception.ClientNotFoundException;
+import io.gravitee.am.service.exception.DomainNotFoundException;
+import io.gravitee.am.service.model.UpdateForm;
+import io.gravitee.common.http.MediaType;
+import io.reactivex.Maybe;
+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.Suspended;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Api(tags = {"form"})
+public class ClientFormResource extends AbstractResource {
+
+    @Autowired
+    private FormService formService;
+
+    @Autowired
+    private DomainService domainService;
+
+    @Autowired
+    private ClientService clientService;
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Update a form for a client")
+    @ApiResponses({
+            @ApiResponse(code = 201, message = "Form successfully updated", response = Form.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void update(
+            @PathParam("domain") String domain,
+            @PathParam("client") String client,
+            @PathParam("form") String form,
+            @ApiParam(name = "form", required = true) @Valid @NotNull UpdateForm updateForm,
+            @Suspended final AsyncResponse response) {
+        domainService.findById(domain)
+                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                .flatMap(irrelevant -> clientService.findById(client))
+                .switchIfEmpty(Maybe.error(new ClientNotFoundException(client)))
+                .flatMapSingle(irrelevant -> formService.update(domain, client, form, updateForm))
+                .map(email1 -> Response.ok(email1).build())
+                .subscribe(
+                        result -> response.resume(result),
+                        error -> response.resume(error));
+    }
+
+    @DELETE
+    @ApiOperation(value = "Delete a form for a client")
+    @ApiResponses({
+            @ApiResponse(code = 204, message = "Form successfully deleted"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void delete(@PathParam("domain") String domain,
+                       @PathParam("form") String form,
+                       @Suspended final AsyncResponse response) {
+        formService.delete(form)
+                .subscribe(
+                        () -> response.resume(Response.noContent().build()),
+                        error -> response.resume(error));
+    }
+
+}
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/ClientFormsResource.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/ClientFormsResource.java
new file mode 100644
index 0000000000..a99f18f593
--- /dev/null
+++ 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/ClientFormsResource.java
@@ -0,0 +1,110 @@
+/**
+ * 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.model.Template;
+import io.gravitee.am.service.ClientService;
+import io.gravitee.am.service.DomainService;
+import io.gravitee.am.service.FormService;
+import io.gravitee.am.service.exception.ClientNotFoundException;
+import io.gravitee.am.service.exception.DomainNotFoundException;
+import io.gravitee.am.service.model.NewForm;
+import io.gravitee.common.http.HttpStatusCode;
+import io.gravitee.common.http.MediaType;
+import io.reactivex.Maybe;
+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;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Api(tags = {"form"})
+public class ClientFormsResource extends AbstractResource {
+
+    @Autowired
+    private FormService formService;
+
+    @Autowired
+    private DomainService domainService;
+
+    @Autowired
+    private ClientService clientService;
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Find a form for a client")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Form successfully fetched"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void get(
+            @PathParam("domain") String domain,
+            @PathParam("client") String client,
+            @NotNull @QueryParam("template") Template emailTemplate,
+            @Suspended final AsyncResponse response) {
+        formService.findByDomainAndClientAndTemplate(domain, client, emailTemplate.template())
+                .map(form -> Response.ok(form).build())
+                .defaultIfEmpty(Response.status(HttpStatusCode.NOT_FOUND_404).build())
+                .subscribe(
+                        result -> response.resume(result),
+                        error -> response.resume(error));
+    }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Create a form for a client")
+    @ApiResponses({
+            @ApiResponse(code = 201, message = "Form successfully created"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void create(
+            @PathParam("domain") String domain,
+            @PathParam("client") String client,
+            @ApiParam(name = "email", required = true)
+            @Valid @NotNull final NewForm newForm,
+            @Suspended final AsyncResponse response) {
+        domainService.findById(domain)
+                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                .flatMap(irrelevant -> clientService.findById(client))
+                .switchIfEmpty(Maybe.error(new ClientNotFoundException(client)))
+                .flatMapSingle(irrelevant -> formService.create(domain, client, newForm))
+                .map(form -> Response
+                        .created(URI.create("/domains/" + domain + "/clients/" + client + "/forms/" + form.getId()))
+                        .entity(form)
+                        .build())
+                .subscribe(
+                        result -> response.resume(result),
+                        error -> response.resume(error));
+    }
+
+    @Path("{form}")
+    public ClientFormResource getFormResource() {
+        return resourceContext.getResource(ClientFormResource.class);
+    }
+}
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/ClientResource.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/ClientResource.java
index d1a65cc615..948bc96044 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/ClientResource.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/ClientResource.java
@@ -146,6 +146,11 @@ public class ClientResource extends AbstractResource {
         return resourceContext.getResource(ClientEmailsResource.class);
     }
 
+    @Path("forms")
+    public ClientFormsResource getFormsResource() {
+        return resourceContext.getResource(ClientFormsResource.class);
+    }
+
     /**
      * Before dynamic client registration feature, response_type field was not managed.
      * In order to protect those who were using the PUT API without this new field, we'll add default value.
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/ClientsResource.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/ClientsResource.java
index 65ae5e5995..9bdf36d325 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/ClientsResource.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/ClientsResource.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.management.handlers.management.api.resources;
 
 import io.gravitee.am.management.handlers.management.api.resources.enhancer.ClientEnhancer;
+import io.gravitee.am.model.Client;
 import io.gravitee.am.model.ClientListItem;
 import io.gravitee.am.service.ClientService;
 import io.gravitee.am.service.DomainService;
@@ -23,6 +24,7 @@ import io.gravitee.am.service.exception.DomainNotFoundException;
 import io.gravitee.am.service.model.NewClient;
 import io.gravitee.common.http.MediaType;
 import io.reactivex.Maybe;
+import io.reactivex.Single;
 import io.swagger.annotations.*;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -37,6 +39,7 @@ import javax.ws.rs.core.Response;
 import java.net.URI;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -67,10 +70,11 @@ public class ClientsResource extends AbstractResource {
                     response = ClientListItem.class, responseContainer = "Set"),
             @ApiResponse(code = 500, message = "Internal server error")})
     public void list(@PathParam("domain") String _domain,
-                            @Suspended final AsyncResponse response) {
+                     @QueryParam("q") String query,
+                     @Suspended final AsyncResponse response) {
         domainService.findById(_domain)
                 .switchIfEmpty(Maybe.error(new DomainNotFoundException(_domain)))
-                .flatMapSingle(domain -> clientService.findByDomain(_domain)
+                .flatMapSingle(domain -> getDomains(_domain, query)
                         .map(clients -> {
                             List<ClientListItem> sortedClients = clients.stream()
                                     .map(clientEnhancer.enhanceClient(Collections.singletonMap(_domain, domain)))
@@ -113,4 +117,12 @@ public class ClientsResource extends AbstractResource {
     public ClientResource getClientResource() {
         return resourceContext.getResource(ClientResource.class);
     }
+
+    private Single<Set<Client>> getDomains(String domainId, String query) {
+        if (query != null) {
+            return clientService.search(domainId, query);
+        } else {
+            return clientService.findByDomain(domainId);
+        }
+    }
 }
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/FormResource.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/FormResource.java
index f7307ac914..a2d2b759c4 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/FormResource.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/FormResource.java
@@ -67,7 +67,7 @@ public class FormResource {
         domainService.findById(domain)
                 .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
                 .flatMapSingle(irrelevant -> formService.update(domain, form, updateForm))
-                .map(page1 -> Response.ok(page1).build())
+                .map(form1 -> Response.ok(form1).build())
                 .subscribe(
                         result -> response.resume(result),
                         error -> response.resume(error));
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/enhancer/ClientEnhancer.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/enhancer/ClientEnhancer.java
index 3006620374..df84e51a67 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/enhancer/ClientEnhancer.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/enhancer/ClientEnhancer.java
@@ -53,7 +53,7 @@ public class ClientEnhancer {
                 clientListItem.setDomainId(domains.get(client.getDomain()).getId());
                 clientListItem.setDomainName(domains.get(client.getDomain()).getName());
             } else {
-                clientListItem.setDomainId("unkown-domain");
+                clientListItem.setDomainId("unknown-domain");
                 clientListItem.setDomainName("Unknown domain");
             }
             clientListItem.setClientName(client.getClientName());
diff --git a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/ClientRepositoryProxy.java b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/ClientRepositoryProxy.java
index 197ddf6eaf..6c9f9f9891 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/ClientRepositoryProxy.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/ClientRepositoryProxy.java
@@ -37,6 +37,11 @@ public class ClientRepositoryProxy extends AbstractProxy<ClientRepository> imple
         return target.findByDomain(domain);
     }
 
+    @Override
+    public Single<Set<Client>> search(String domain, String query) {
+        return target.search(domain, query);
+    }
+
     @Override
     public Single<Page<Client>> findByDomain(String domain, int page, int size) {
         return target.findByDomain(domain, page, size);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/EmailRepositoryProxy.java b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/EmailRepositoryProxy.java
index d143a95730..2088e98224 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/EmailRepositoryProxy.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/EmailRepositoryProxy.java
@@ -41,6 +41,11 @@ public class EmailRepositoryProxy extends AbstractProxy<EmailRepository> impleme
         return target.findByDomain(domain);
     }
 
+    @Override
+    public Single<List<Email>> findByDomainAndClient(String domain, String client) {
+        return target.findByDomainAndClient(domain, client);
+    }
+
     @Override
     public Maybe<Email> findByDomainAndTemplate(String domain, String template) {
         return target.findByDomainAndTemplate(domain, template);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/FormRepositoryProxy.java b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/FormRepositoryProxy.java
index 1ce96cee5a..afba4faeef 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/FormRepositoryProxy.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-repository/src/main/java/io/gravitee/am/management/repository/proxy/FormRepositoryProxy.java
@@ -36,6 +36,11 @@ public class FormRepositoryProxy extends AbstractProxy<FormRepository> implement
         return target.findByDomain(domain);
     }
 
+    @Override
+    public Single<List<Form>> findByDomainAndClient(String domain, String client) {
+        return target.findByDomainAndClient(domain, client);
+    }
+
     @Override
     public Maybe<Form> findByDomainAndTemplate(String domain, String template) {
         return target.findByDomainAndTemplate(domain, template);
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 52f5c58cde..8901a4dbbd 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
@@ -247,6 +247,9 @@ public class UserServiceImpl implements UserService {
         claims.put(Claims.iat, new Date().getTime() / 1000);
         claims.put(Claims.exp, new Date(System.currentTimeMillis() + (expiresAfter * 1000)).getTime() / 1000);
         claims.put(Claims.sub, user.getId());
+        if (user.getClient() != null) {
+            claims.put(Claims.aud, user.getClient());
+        }
         claims.put(StandardClaims.EMAIL, user.getEmail());
         claims.put(StandardClaims.GIVEN_NAME, user.getFirstName());
         claims.put(StandardClaims.FAMILY_NAME, user.getLastName());
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/ClientRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/ClientRepository.java
index be5a5120c0..fbf9ca0321 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/ClientRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/ClientRepository.java
@@ -31,6 +31,8 @@ public interface ClientRepository extends CrudRepository<Client, String> {
 
     Single<Set<Client>> findByDomain(String domain);
 
+    Single<Set<Client>> search(String domain, String query);
+
     Single<Page<Client>> findByDomain(String domain, int page, int size);
 
     Maybe<Client> findByClientIdAndDomain(String clientId, String domain);
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EmailRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EmailRepository.java
index a5bd1390b8..44c7722b4a 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EmailRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EmailRepository.java
@@ -32,6 +32,8 @@ public interface EmailRepository extends CrudRepository<Email, String> {
 
     Single<List<Email>> findByDomain(String domain);
 
+    Single<List<Email>> findByDomainAndClient(String domain, String client);
+
     Maybe<Email> findByDomainAndTemplate(String domain, String template);
 
     Maybe<Email> findByDomainAndClientAndTemplate(String domain, String client, String template);
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FormRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FormRepository.java
index 0cc8b97d52..6c815bff76 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FormRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FormRepository.java
@@ -30,6 +30,8 @@ public interface FormRepository extends CrudRepository<Form, String> {
 
     Single<List<Form>> findByDomain(String domain);
 
+    Single<List<Form>> findByDomainAndClient(String domain, String client);
+
     Maybe<Form> findByDomainAndTemplate(String domain, String template);
 
     Maybe<Form> findByDomainAndClientAndTemplate(String domain, String client, String template);
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 9763fe33c5..4108cead13 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
@@ -33,6 +33,7 @@ import io.reactivex.Maybe;
 import io.reactivex.Observable;
 import io.reactivex.Single;
 import org.bson.Document;
+import org.bson.conversions.Bson;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -40,6 +41,7 @@ import javax.annotation.PostConstruct;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import static com.mongodb.client.model.Filters.*;
@@ -80,6 +82,16 @@ public class MongoClientRepository extends AbstractManagementMongoRepository imp
         return Observable.fromPublisher(clientsCollection.find(eq(FIELD_DOMAIN, domain))).map(this::convert).collect(HashSet::new, Set::add);
     }
 
+    @Override
+    public Single<Set<Client>> search(String domain, String query) {
+        // currently search on client_id field
+        Bson mongoQuery = and(
+                eq(FIELD_DOMAIN, domain),
+                regex(FIELD_CLIENT_ID, "^(?)" + Pattern.quote(query), "i"));
+
+        return Observable.fromPublisher(clientsCollection.find(mongoQuery)).map(this::convert).collect(HashSet::new, Set::add);
+    }
+
     @Override
     public Single<Page<Client>> findByDomain(String domain, int page, int size) {
         Single<Long> countOperation = Observable.fromPublisher(clientsCollection.count(eq(FIELD_DOMAIN, domain))).first(0l);
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 b8a836e0c6..da52f163f6 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
@@ -69,6 +69,16 @@ public class MongoEmailRepository extends AbstractManagementMongoRepository impl
         return Observable.fromPublisher(emailsCollection.find(eq(FIELD_DOMAIN, domain))).map(this::convert).collect(ArrayList::new, List::add);
     }
 
+    @Override
+    public Single<List<Email>> findByDomainAndClient(String domain, String client) {
+        return Observable.fromPublisher(
+                emailsCollection.find(
+                        and(
+                                eq(FIELD_DOMAIN, domain),
+                                eq(FIELD_CLIENT, client))
+                        )).map(this::convert).collect(ArrayList::new, List::add);
+    }
+
     @Override
     public Maybe<Email> findByDomainAndTemplate(String domain, String template) {
         return Observable.fromPublisher(
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 e175ca2dd3..5042e24b8f 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
@@ -33,8 +33,7 @@ import javax.annotation.PostConstruct;
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.mongodb.client.model.Filters.and;
-import static com.mongodb.client.model.Filters.eq;
+import static com.mongodb.client.model.Filters.*;
 
 /**
  * @author Titouan COMPIEGNE (david.brassely at graviteesource.com)
@@ -65,13 +64,24 @@ public class MongoFormRepository extends AbstractManagementMongoRepository imple
         return Observable.fromPublisher(formsCollection.find(eq(FIELD_DOMAIN, domain))).map(this::convert).collect(ArrayList::new, List::add);
     }
 
+    @Override
+    public Single<List<Form>> findByDomainAndClient(String domain, String client) {
+        return Observable.fromPublisher(
+                formsCollection.find(
+                        and(
+                                eq(FIELD_DOMAIN, domain),
+                                eq(FIELD_CLIENT, client))
+                        )).map(this::convert).collect(ArrayList::new, List::add);
+    }
+
     @Override
     public Maybe<Form> findByDomainAndTemplate(String domain, String template) {
         return Observable.fromPublisher(
                 formsCollection.find(
                         and(
                                 eq(FIELD_DOMAIN, domain),
-                                eq(FIELD_TEMPLATE, template)))
+                                eq(FIELD_TEMPLATE, template),
+                                exists(FIELD_CLIENT, false)))
                         .first())
                 .firstElement().map(this::convert);
     }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/ClientService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/ClientService.java
index 1dee3bff7f..10f6c1590f 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/ClientService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/ClientService.java
@@ -38,6 +38,8 @@ public interface ClientService {
 
     Single<Page<Client>> findByDomain(String domain, int page, int size);
 
+    Single<Set<Client>> search(String domain, String query);
+
     Single<Set<Client>> findByDomain(String domain);
 
     Single<Client> create(String domain, NewClient newClient);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/EmailTemplateService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/EmailTemplateService.java
index 0f053db9c1..f572244405 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/EmailTemplateService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/EmailTemplateService.java
@@ -32,6 +32,10 @@ public interface EmailTemplateService {
 
     Single<List<Email>> findAll();
 
+    Single<List<Email>> findByDomain(String domain);
+
+    Single<List<Email>> findByDomainAndClient(String domain, String client);
+
     Maybe<Email> findByDomainAndTemplate(String domain, String template);
 
     Maybe<Email> findByDomainAndClientAndTemplate(String domain, String client, String template);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/FormService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/FormService.java
index e0809d8592..3fcf581c72 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/FormService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/FormService.java
@@ -22,18 +22,30 @@ import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 
+import java.util.List;
+
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
  */
 public interface FormService {
 
+    Single<List<Form>> findByDomain(String domain);
+
+    Single<List<Form>> findByDomainAndClient(String domain, String client);
+
     Maybe<Form> findByDomainAndTemplate(String domain, String template);
 
+    Maybe<Form> findByDomainAndClientAndTemplate(String domain, String client, String template);
+
     Single<Form> create(String domain, NewForm form);
 
+    Single<Form> create(String domain, String client, NewForm form);
+
     Single<Form> update(String domain, String id, UpdateForm form);
 
+    Single<Form> update(String domain, String client, String id, UpdateForm form);
+
     Completable delete(String pageId);
 
 }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ClientServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ClientServiceImpl.java
index e4a88c3117..3fa320254e 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ClientServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ClientServiceImpl.java
@@ -24,10 +24,7 @@ import io.gravitee.am.model.common.event.Payload;
 import io.gravitee.am.model.common.event.Type;
 import io.gravitee.am.repository.management.api.ClientRepository;
 import io.gravitee.am.repository.oauth2.api.AccessTokenRepository;
-import io.gravitee.am.service.ClientService;
-import io.gravitee.am.service.DomainService;
-import io.gravitee.am.service.IdentityProviderService;
-import io.gravitee.am.service.ScopeService;
+import io.gravitee.am.service.*;
 import io.gravitee.am.service.exception.AbstractManagementException;
 import io.gravitee.am.service.exception.ClientAlreadyExistsException;
 import io.gravitee.am.service.exception.ClientNotFoundException;
@@ -56,6 +53,7 @@ import org.springframework.stereotype.Component;
 import java.net.URI;
 import java.util.Collections;
 import java.util.Date;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -85,6 +83,12 @@ public class ClientServiceImpl implements ClientService {
     @Autowired
     private ScopeService scopeService;
 
+    @Autowired
+    private EmailTemplateService emailTemplateService;
+
+    @Autowired
+    private FormService formService;
+
     @Override
     public Maybe<Client> findById(String id) {
         LOGGER.debug("Find client by ID: {}", id);
@@ -125,6 +129,17 @@ public class ClientServiceImpl implements ClientService {
                 });
     }
 
+    @Override
+    public Single<Set<Client>> search(String domain, String query) {
+        LOGGER.debug("Search clients for domain {} and with query {}", domain, query);
+        return clientRepository.search(domain, query)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error occurs while trying to find clients for domain {} and query {}", domain, query, ex);
+                    return Single.error(new TechnicalManagementException(
+                            String.format("An error occurs while trying to find clients for domain %s and query %s", domain, query), ex));
+                });
+    }
+
     @Override
     public Single<Page<Client>> findByDomain(String domain, int page, int size) {
         LOGGER.debug("Find clients by domain", domain);
@@ -399,7 +414,22 @@ public class ClientServiceImpl implements ClientService {
                 .flatMapCompletable(client -> {
                     // Reload domain to take care about delete client
                     Event event = new Event(Type.CLIENT, new Payload(client.getId(), client.getDomain(), Action.DELETE));
-                    return clientRepository.delete(clientId).andThen(domainService.reload(client.getDomain(), event).toCompletable());
+                    return clientRepository.delete(clientId)
+                            .andThen(domainService.reload(client.getDomain(), event).toCompletable())
+                            // delete email templates
+                            .andThen(emailTemplateService.findByDomain(client.getDomain())
+                                    .flatMapCompletable(scopes -> {
+                                        List<Completable> deleteEmailsCompletable = scopes.stream().map(e -> emailTemplateService.delete(e.getId())).collect(Collectors.toList());
+                                        return Completable.concat(deleteEmailsCompletable);
+                                    })
+                            )
+                            // delete form templates
+                            .andThen(formService.findByDomain(client.getDomain())
+                                    .flatMapCompletable(scopes -> {
+                                        List<Completable> deleteFormsCompletable = scopes.stream().map(f -> formService.delete(f.getId())).collect(Collectors.toList());
+                                        return Completable.concat(deleteFormsCompletable);
+                                    })
+                            );
                 })
                 .onErrorResumeNext(ex -> {
                     if (ex instanceof AbstractManagementException) {
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java
index c40c611957..c536525764 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java
@@ -23,11 +23,7 @@ import io.gravitee.am.model.common.event.Type;
 import io.gravitee.am.model.oidc.OIDCSettings;
 import io.gravitee.am.repository.management.api.DomainRepository;
 import io.gravitee.am.service.*;
-import io.gravitee.am.service.exception.AbstractManagementException;
-import io.gravitee.am.service.exception.DomainAlreadyExistsException;
-import io.gravitee.am.service.exception.DomainDeleteMasterException;
-import io.gravitee.am.service.exception.DomainNotFoundException;
-import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.exception.*;
 import io.gravitee.am.service.model.NewDomain;
 import io.gravitee.am.service.model.NewSystemScope;
 import io.gravitee.am.service.model.PatchDomain;
@@ -86,6 +82,12 @@ public class DomainServiceImpl implements DomainService {
     @Autowired
     private GroupService groupService;
 
+    @Autowired
+    private EmailTemplateService emailTemplateService;
+
+    @Autowired
+    private FormService formService;
+
     @Override
     public Maybe<Domain> findById(String id) {
         LOGGER.debug("Find domain by ID: {}", id);
@@ -314,7 +316,22 @@ public class DomainServiceImpl implements DomainService {
                                         List<Completable> deleteScopesCompletable = scopes.stream().map(s -> scopeService.delete(s.getId(), true)).collect(Collectors.toList());
                                         return Completable.concat(deleteScopesCompletable);
                                     })
-                            .andThen(domainRepository.delete(domainId)));
+                            )
+                            // delete email templates
+                            .andThen(emailTemplateService.findByDomain(domainId)
+                                    .flatMapCompletable(scopes -> {
+                                        List<Completable> deleteEmailsCompletable = scopes.stream().map(e -> emailTemplateService.delete(e.getId())).collect(Collectors.toList());
+                                        return Completable.concat(deleteEmailsCompletable);
+                                    })
+                            )
+                            // delete form templates
+                            .andThen(formService.findByDomain(domainId)
+                                    .flatMapCompletable(scopes -> {
+                                        List<Completable> deleteFormsCompletable = scopes.stream().map(f -> formService.delete(f.getId())).collect(Collectors.toList());
+                                        return Completable.concat(deleteFormsCompletable);
+                                    })
+                            )
+                            .andThen(domainRepository.delete(domainId));
                 })
                 .onErrorResumeNext(ex -> {
                     if (ex instanceof AbstractManagementException) {
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EmailTemplateServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EmailTemplateServiceImpl.java
index fbb3fee9d1..2101cf8138 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EmailTemplateServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EmailTemplateServiceImpl.java
@@ -66,6 +66,17 @@ public class EmailTemplateServiceImpl implements EmailTemplateService {
                 });
     }
 
+    @Override
+    public Single<List<Email>> findByDomain(String domain) {
+        LOGGER.debug("Find email by domain {}", domain);
+        return emailRepository.findByDomain(domain)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error occurs while trying to find a email using its domain {}", domain, ex);
+                    return Single.error(new TechnicalManagementException(
+                            String.format("An error occurs while trying to find a email using its domain %s", domain), ex));
+                });
+    }
+
     @Override
     public Maybe<Email> findByDomainAndTemplate(String domain, String template) {
         LOGGER.debug("Find email by domain {} and template {}", domain, template);
@@ -77,6 +88,18 @@ public class EmailTemplateServiceImpl implements EmailTemplateService {
                 });
     }
 
+    @Override
+    public Single<List<Email>> findByDomainAndClient(String domain, String client) {
+        LOGGER.debug("Find email by domain {} and client {}", domain, client);
+        return emailRepository.findByDomainAndClient(domain, client)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error occurs while trying to find a email using its domain {} and client {}", domain, client, ex);
+                    return Single.error(new TechnicalManagementException(
+                            String.format("An error occurs while trying to find a email using its domain %s and client %s", domain, client), ex));
+                });
+    }
+
+
     @Override
     public Maybe<Email> findByDomainAndClientAndTemplate(String domain, String client, String template) {
         LOGGER.debug("Find email by domain {}, client {} and template {}", domain, client, template);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FormServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FormServiceImpl.java
index cdd6a8758f..e22912cb8a 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FormServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FormServiceImpl.java
@@ -39,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -55,6 +56,28 @@ public class FormServiceImpl implements FormService {
     @Autowired
     private DomainService domainService;
 
+    @Override
+    public Single<List<Form>> findByDomain(String domain) {
+        LOGGER.debug("Find form by domain {}", domain);
+        return formRepository.findByDomain(domain)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error occurs while trying to find a form using its domain {}", domain, ex);
+                    return Single.error(new TechnicalManagementException(
+                            String.format("An error occurs while trying to find a role using its domain %s and template %s", domain), ex));
+                });
+    }
+
+    @Override
+    public Single<List<Form>> findByDomainAndClient(String domain, String client) {
+        LOGGER.debug("Find form by domain {} and client {}", domain, client);
+        return formRepository.findByDomainAndClient(domain, client)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error occurs while trying to find a form using its domain {} and client {}", domain, client, ex);
+                    return Single.error(new TechnicalManagementException(
+                            String.format("An error occurs while trying to find a role using its domain %s and client %s", domain, client), ex));
+                });
+    }
+
     @Override
     public Maybe<Form> findByDomainAndTemplate(String domain, String template) {
         LOGGER.debug("Find form by domain {} and template {}", domain, template);
@@ -66,25 +89,58 @@ public class FormServiceImpl implements FormService {
                 });
     }
 
+    @Override
+    public Maybe<Form> findByDomainAndClientAndTemplate(String domain, String client, String template) {
+        LOGGER.debug("Find form by domain {}, client {} and template {}", domain, client, template);
+        return formRepository.findByDomainAndClientAndTemplate(domain, client, template)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error occurs while trying to find a form using its domain {} its client {} and template {}", domain, client, template, ex);
+                    return Maybe.error(new TechnicalManagementException(
+                            String.format("An error occurs while trying to find a form using its domain %s its client %s and template %s", domain, client, template), ex));
+                });
+    }
+
     @Override
     public Single<Form> create(String domain, NewForm newForm) {
-        LOGGER.debug("Create a new page {} for domain {}", newForm, domain);
+        LOGGER.debug("Create a new form {} for domain {}", newForm, domain);
+        return create0(domain, null, newForm);
+    }
 
-        String pageId = UUID.toString(UUID.random());
+    @Override
+    public Single<Form> create(String domain, String client, NewForm newForm) {
+        LOGGER.debug("Create a new form {} for domain {} and client {}", newForm, domain, client);
+        return create0(domain, client, newForm);
+    }
+
+    @Override
+    public Single<Form> update(String domain, String id, UpdateForm updateForm) {
+        LOGGER.debug("Update a form {} for domain {}", id, domain);
+        return update0(domain, id, updateForm);
+    }
+
+    @Override
+    public Single<Form> update(String domain, String client, String id, UpdateForm updateForm) {
+        LOGGER.debug("Update a form {} for domain {} and client {}", id, domain, client);
+        return update0(domain, id, updateForm);
+    }
+
+    private Single<Form> create0(String domain, String client, NewForm newForm) {
+        String formId = UUID.toString(UUID.random());
 
         // check if form is unique
-        return checkFormUniqueness(domain, newForm.getTemplate().template())
+        return checkFormUniqueness(domain, client, newForm.getTemplate().template())
                 .flatMap(irrelevant -> {
-                    Form page = new Form();
-                    page.setId(pageId);
-                    page.setDomain(domain);
-                    page.setEnabled(newForm.isEnabled());
-                    page.setTemplate(newForm.getTemplate().template());
-                    page.setContent(newForm.getContent());
-                    page.setAssets(newForm.getAssets());
-                    page.setCreatedAt(new Date());
-                    page.setUpdatedAt(page.getCreatedAt());
-                    return formRepository.create(page);
+                    Form form = new Form();
+                    form.setId(formId);
+                    form.setDomain(domain);
+                    form.setClient(client);
+                    form.setEnabled(newForm.isEnabled());
+                    form.setTemplate(newForm.getTemplate().template());
+                    form.setContent(newForm.getContent());
+                    form.setAssets(newForm.getAssets());
+                    form.setCreatedAt(new Date());
+                    form.setUpdatedAt(form.getCreatedAt());
+                    return formRepository.create(form);
                 })
                 .flatMap(page -> {
                     // Reload domain to take care about page creation
@@ -101,18 +157,16 @@ public class FormServiceImpl implements FormService {
                 });
     }
 
-    @Override
-    public Single<Form> update(String domain, String id, UpdateForm updateForm) {
-        LOGGER.debug("Update a page {} for domain {}", id, domain);
+    private Single<Form> update0(String domain, String id, UpdateForm updateForm) {
         return formRepository.findById(id)
                 .switchIfEmpty(Maybe.error(new FormNotFoundException(id)))
-                .flatMapSingle(oldPage -> {
-                    oldPage.setEnabled(updateForm.isEnabled());
-                    oldPage.setContent(updateForm.getContent());
-                    oldPage.setAssets(updateForm.getAssets());
-                    oldPage.setUpdatedAt(new Date());
+                .flatMapSingle(oldForm -> {
+                    oldForm.setEnabled(updateForm.isEnabled());
+                    oldForm.setContent(updateForm.getContent());
+                    oldForm.setAssets(updateForm.getAssets());
+                    oldForm.setUpdatedAt(new Date());
 
-                    return formRepository.update(oldPage);
+                    return formRepository.update(oldForm);
                 })
                 .flatMap(page -> {
                     // Reload domain to take care about form update
@@ -129,6 +183,7 @@ public class FormServiceImpl implements FormService {
                 });
     }
 
+
     @Override
     public Completable delete(String formId) {
         LOGGER.debug("Delete form {}", formId);
@@ -150,8 +205,14 @@ public class FormServiceImpl implements FormService {
                 });
     }
 
-    private Single<Boolean> checkFormUniqueness(String domain, String formTemplate) {
-        return findByDomainAndTemplate(domain, formTemplate)
+
+
+    private Single<Boolean> checkFormUniqueness(String domain, String client, String formTemplate) {
+        Maybe<Form> maybeSource = client == null ?
+                findByDomainAndTemplate(domain, formTemplate) :
+                findByDomainAndClientAndTemplate(domain, client, formTemplate);
+
+        return maybeSource
                 .isEmpty()
                 .map(isEmpty -> {
                     if (!isEmpty) {
diff --git a/gravitee-am-ui/src/app/app-routing.module.ts b/gravitee-am-ui/src/app/app-routing.module.ts
index b827fce23f..b426e43a37 100644
--- a/gravitee-am-ui/src/app/app-routing.module.ts
+++ b/gravitee-am-ui/src/app/app-routing.module.ts
@@ -28,6 +28,7 @@ import {DomainSettingsProvidersComponent} from "./domain/settings/providers/prov
 import {DomainSettingsRolesComponent} from "./domain/settings/roles/roles.component";
 import {DomainSettingsScopesComponent} from "./domain/settings/scopes/scopes.component";
 import {DomainSettingsFormsComponent} from "./domain/settings/forms/forms.component";
+import {DomainSettingsFormComponent} from "./domain/settings/forms/form/form.component";
 import {DomainSettingsLoginComponent} from "./domain/settings/login/login.component";
 import {DomainSettingsEmailsComponent} from "./domain/settings/emails/emails.component";
 import {DomainSettingsEmailComponent} from "./domain/settings/emails/email/email.component";
@@ -54,6 +55,8 @@ import {ClientSettingsComponent} from "./domain/clients/client/settings/settings
 import {ClientIdPComponent} from "./domain/clients/client/idp/idp.component";
 import {ClientEmailsComponent} from "./domain/clients/client/emails/emails.component";
 import {ClientEmailComponent} from "./domain/clients/client/emails/email/email.component";
+import {ClientFormsComponent} from "./domain/clients/client/forms/forms.component";
+import {ClientFormComponent} from "./domain/clients/client/forms/form/form.component";
 import {CertificatesResolver} from "./resolvers/certificates.resolver";
 import {CertificateCreationComponent} from "./domain/settings/certificates/creation/certificate-creation.component";
 import {CertificateComponent} from "./domain/settings/certificates/certificate/certificate.component";
@@ -80,7 +83,6 @@ import {ExtensionGrantsResolver} from "./resolvers/extension-grants.resolver";
 import {ExtensionGrantResolver} from "./resolvers/extension-grant.resolver";
 import {ManagementComponent} from "./settings/management/management.component";
 import {ManagementGeneralComponent} from "./settings/management/general/general.component";
-import {FormComponent} from "./domain/settings/forms/form/form.component";
 import {FormResolver} from "./resolvers/form.resolver";
 import {GroupsResolver} from "./resolvers/groups.resolver";
 import {GroupsComponent} from "./domain/settings/groups/groups.component";
@@ -178,7 +180,7 @@ const routes: Routes = [
           },
           {
             path: 'login',
-            component: FormComponent,
+            component: DomainSettingsFormComponent,
             resolve: {
               form: FormResolver
             },
@@ -338,7 +340,9 @@ const routes: Routes = [
           },
           { path: 'idp', component: ClientIdPComponent },
           { path: 'oidc', component: ClientOIDCComponent },
-          { path: 'emails', component: ClientEmailsComponent },
+          { path: 'forms', component: ClientFormsComponent, resolve: { domain: DomainResolver } },
+          { path: 'forms/form', component: ClientFormComponent, resolve: { form: FormResolver } },
+          { path: 'emails', component: ClientEmailsComponent, resolve: { domain: DomainResolver } },
           { path: 'emails/email', component: ClientEmailComponent, resolve: { email: EmailResolver} }
         ]
       },
@@ -493,6 +497,7 @@ const routes: Routes = [
           },
           { path: 'forms',
             component: DomainSettingsFormsComponent,
+            resolve: { domain: DomainResolver },
             data: {
               menu: {
                 label: 'Forms',
@@ -501,13 +506,14 @@ const routes: Routes = [
             }
           },
           { path: 'forms/form',
-            component: FormComponent,
+            component: DomainSettingsFormComponent,
             resolve: {
               form: FormResolver
             },
           },
           { path: 'emails',
             component: DomainSettingsEmailsComponent,
+            resolve: { domain: DomainResolver },
             data: {
               menu: {
                 label: 'Emails',
diff --git a/gravitee-am-ui/src/app/app.module.ts b/gravitee-am-ui/src/app/app.module.ts
index 46ed191a8f..0c96f32e36 100644
--- a/gravitee-am-ui/src/app/app.module.ts
+++ b/gravitee-am-ui/src/app/app.module.ts
@@ -63,9 +63,12 @@ import { ProviderCreationComponent } from './domain/settings/providers/creation/
 import { ClientComponent } from './domain/clients/client/client.component';
 import { ClientCreationComponent } from './clients/creation/client-creation.component';
 import { ClientSettingsComponent } from './domain/clients/client/settings/settings.component';
+import { ClientIdPComponent } from './domain/clients/client/idp/idp.component';
 import { ClientOIDCComponent, CreateClaimComponent } from './domain/clients/client/oidc/oidc.component';
 import { ClientEmailsComponent } from './domain/clients/client/emails/emails.component';
 import { ClientEmailComponent } from './domain/clients/client/emails/email/email.component';
+import { ClientFormsComponent } from './domain/clients/client/forms/forms.component';
+import { ClientFormComponent } from './domain/clients/client/forms/form/form.component';
 import { ProviderCreationStep1Component } from './domain/settings/providers/creation/steps/step1/step1.component';
 import { ProviderCreationStep2Component } from './domain/settings/providers/creation/steps/step2/step2.component';
 import { ProviderComponent } from './domain/settings/providers/provider/provider.component';
@@ -91,6 +94,7 @@ import { DomainSettingsCertificatesComponent, CertitificatePublicKeyDialog } fro
 import { DomainSettingsProvidersComponent } from "./domain/settings/providers/providers.component";
 import { DomainSettingsExtensionGrantsComponent } from "./domain/settings/extension-grants/extension-grants.component";
 import { DomainSettingsFormsComponent } from "./domain/settings/forms/forms.component";
+import { DomainSettingsFormComponent } from "./domain/settings/forms/form/form.component";
 import { DomainSettingsLoginComponent } from "./domain/settings/login/login.component";
 import { DomainSettingsEmailsComponent } from "./domain/settings/emails/emails.component";
 import { DomainSettingsEmailComponent } from "./domain/settings/emails/email/email.component";
@@ -122,7 +126,6 @@ import { ScopeResolver } from "./resolvers/scope.resolver";
 import { ScopeCreationComponent } from './domain/settings/scopes/creation/scope-creation.component';
 import { ScopeComponent } from './domain/settings/scopes/scope/scope.component';
 import { SnackbarComponent } from "./components/snackbar/snackbar.component";
-import { ClientIdPComponent } from './domain/clients/client/idp/idp.component';
 import { NavbarComponent } from './components/navbar/navbar.component';
 import { DashboardComponent } from './dashboard/dashboard.component';
 import { DashboardService} from "./services/dashboard.service";
@@ -152,7 +155,8 @@ import { ExtensionGrantCreationStep2Component } from "./domain/settings/extensio
 import { MaterialFileComponent } from "./components/json-schema-form/material-file.component";
 import { ManagementComponent } from "./settings/management/management.component";
 import { ManagementGeneralComponent } from "./settings/management/general/general.component";
-import { FormComponent, FormInfoDialog } from "./domain/settings/forms/form/form.component";
+import { FormsComponent } from "./domain/components/forms/forms.component";
+import { FormComponent, FormInfoDialog } from "./domain/components/forms/form/form.component";
 import { FormService } from "./services/form.service";
 import { FormResolver } from "./resolvers/form.resolver";
 import { GroupsComponent } from "./domain/settings/groups/groups.component";
@@ -189,6 +193,7 @@ import { SelectClientsComponent } from "./domain/components/clients/select-clien
     DomainSettingsCertificatesComponent,
     DomainSettingsExtensionGrantsComponent,
     DomainSettingsFormsComponent,
+    DomainSettingsFormComponent,
     DomainSettingsLoginComponent,
     DomainSettingsEmailsComponent,
     DomainSettingsEmailComponent,
@@ -204,6 +209,8 @@ import { SelectClientsComponent } from "./domain/components/clients/select-clien
     ClientIdPComponent,
     ClientEmailsComponent,
     ClientEmailComponent,
+    ClientFormsComponent,
+    ClientFormComponent,
     ProviderCreationStep1Component,
     ProviderCreationStep2Component,
     ProviderComponent,
@@ -250,6 +257,7 @@ import { SelectClientsComponent } from "./domain/components/clients/select-clien
     MaterialFileComponent,
     ManagementComponent,
     ManagementGeneralComponent,
+    FormsComponent,
     FormComponent,
     FormInfoDialog,
     GroupsComponent,
diff --git a/gravitee-am-ui/src/app/domain/clients/client/client.component.ts b/gravitee-am-ui/src/app/domain/clients/client/client.component.ts
index 8d8d006fe5..92e095a438 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/client.component.ts
+++ b/gravitee-am-ui/src/app/domain/clients/client/client.component.ts
@@ -29,6 +29,7 @@ export class ClientComponent implements OnInit {
     {'href': 'settings' , 'label': 'Settings'},
     {'href': 'idp' , 'label': 'Identity Providers'},
     {'href': 'oidc' , 'label': 'OpenID Connect'},
+    {'href': 'forms' , 'label': 'Forms'},
     {'href': 'emails' , 'label': 'Emails'}
   ];
 
@@ -45,6 +46,7 @@ export class ClientComponent implements OnInit {
     this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/clients/'+this.client.id+'$', this.client.clientId);
     this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/clients/'+this.client.id+'/idp$', 'IdP');
     this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/clients/'+this.client.id+'/oidc$', 'OIDC');
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/clients/'+this.client.id+'/forms', 'Forms');
     this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/clients/'+this.client.id+'/emails$', 'Emails');
   }
 }
diff --git a/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.html b/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.html
index 95f5396db9..d6c20bde59 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.html
+++ b/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.html
@@ -16,7 +16,7 @@
 
 -->
 <div class="client-emails-container" fxFlex="70">
-  <app-emails></app-emails>
+  <app-emails [emails]="emails"></app-emails>
 </div>
 <div class="client-emails-description" fxFlex>
   <h3>Client's email templates</h3>
diff --git a/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.ts b/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.ts
index 029dd74861..13802fbeea 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.ts
+++ b/gravitee-am-ui/src/app/domain/clients/client/emails/emails.component.ts
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 import { Component } from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
 
 @Component({
   selector: 'app-client-emails',
@@ -21,6 +22,41 @@ import { Component } from '@angular/core';
   styleUrls: ['./emails.component.scss']
 })
 export class ClientEmailsComponent {
+  emails: any[];
+  client: any;
+  domain: any;
 
-  constructor() { }
+  constructor(private route: ActivatedRoute) { }
+
+  ngOnInit() {
+    this.domain = this.route.snapshot.data['domain'];
+    this.client = this.route.snapshot.parent.data['client'];
+    this.emails = this.getEmails();
+  }
+
+  getEmails() {
+    return [
+      {
+        'name': 'Registration confirmation',
+        'description': 'Registration email to confirm user account',
+        'template': 'REGISTRATION_CONFIRMATION',
+        'enabled': this.clientSettingsValid()
+      },
+      {
+        'name': 'Reset password',
+        'description': 'Reset password email to ask for a new one',
+        'template': 'RESET_PASSWORD',
+        'enabled': this.clientSettingsValid() && this.allowResetPassword()
+      }
+    ]
+  }
+
+  clientSettingsValid() {
+    let authorizedGrantTypes: string[] = this.client.authorizedGrantTypes
+    return authorizedGrantTypes && (authorizedGrantTypes.includes('authorization_code') || authorizedGrantTypes.includes("implicit"));
+  }
+
+  allowResetPassword() {
+    return this.domain.loginSettings && this.domain.loginSettings.forgotPasswordEnabled;
+  }
 }
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.html b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.html
new file mode 100644
index 0000000000..57479c288c
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.html
@@ -0,0 +1,38 @@
+<!--
+
+    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.
+
+-->
+<div class="client-form-container" fxFlex="70">
+  <a [routerLink]="['..']"><small><< Back to forms</small></a>
+  <div class="client-form-content">
+    <app-form></app-form>
+  </div>
+</div>
+<div class="client-form-description" fxFlex>
+  <h3>Client's form template</h3>
+  <div class="client-form-description-content">
+    <p>
+      You can customize the whole page's content from the editor for each form template.
+    </p>
+    <p>
+      You can use <code>Thymeleaf</code> syntax to dynamically generate content and access User attributes like :
+      <small fxLayout="column" style="margin-top: 10px;">
+        <code><![CDATA[<p th:text="${user.firstName}"></p>]]></code>
+        <code><![CDATA[<p th:text="${user.lastName}"></p>]]></code>
+      </small>
+    </p>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.scss b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.scss
new file mode 100644
index 0000000000..fc17078559
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.scss
@@ -0,0 +1,34 @@
+.client-form-container {
+  a {
+    color: #006CAB;
+    text-decoration: none;
+    &:hover {
+      color: #2196f3;
+      text-decoration: underline;
+    }
+  }
+  .client-form-content {
+    margin-top: 10px;
+    padding: 0px 20px 20px 20px;
+    border-radius: 2px;
+    border: 1px solid rgb(226, 229, 231);
+  }
+}
+
+.client-form-description {
+  margin-left: 20px;
+  h3 {
+    font-weight: 400;
+  }
+  .client-form-description-content {
+    border-top: 1px solid rgb(226, 229, 231);
+    p {
+      margin-top: 20px;
+      font-size: 14px;
+    }
+    small {
+      font-size: 12px;
+      color: rgba(0, 0, 0, 0.38);
+    }
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.spec.ts b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.spec.ts
new file mode 100644
index 0000000000..1221998022
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EmailComponent } from './email.component';
+
+describe('EmailComponent', () => {
+  let component: EmailComponent;
+  let fixture: ComponentFixture<EmailComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ EmailComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EmailComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.ts b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.ts
new file mode 100644
index 0000000000..a5abeb5b08
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/form/form.component.ts
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { BreadcrumbService } from "../../../../../../libraries/ng2-breadcrumb/components/breadcrumbService";
+
+
+@Component({
+  selector: 'app-client-form',
+  templateUrl: './form.component.html',
+  styleUrls: ['./form.component.scss']
+})
+export class ClientFormComponent implements OnInit {
+  private domainId: string;
+  private clientId: string;
+  private rawTemplate: string;
+  private template: string;
+
+  constructor(private route: ActivatedRoute,
+              private breadcrumbService: BreadcrumbService) { }
+
+  ngOnInit() {
+    this.domainId = this.route.snapshot.parent.parent.params['domainId'];
+    this.clientId = this.route.snapshot.parent.params['clientId'];
+    this.rawTemplate = this.route.snapshot.queryParams['template'];
+    this.template = this.rawTemplate.toLowerCase().replace(/_/g, ' ');
+    this.initBreadcrumb();
+  }
+
+  initBreadcrumb() {
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/clients/'+this.clientId+'/forms/form*', this.template);
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.html b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.html
new file mode 100644
index 0000000000..d6a1b3715b
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.html
@@ -0,0 +1,28 @@
+<!--
+
+    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.
+
+-->
+<div class="client-forms-container" fxFlex="70">
+  <app-forms [forms]="forms"></app-forms>
+</div>
+<div class="client-forms-description" fxFlex>
+  <h3>Client's form templates</h3>
+  <div class="client-forms-description-content">
+    <p>
+      Client form templates allows you to display custom web pages to fit your corporate identity guidelines.
+    </p>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.scss b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.scss
new file mode 100644
index 0000000000..1e7dda2880
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.scss
@@ -0,0 +1,24 @@
+.client-forms-container {
+  padding: 0px 20px 0px 20px;
+  p {
+    font-size: small;
+  }
+}
+
+.client-forms-description {
+  margin-left: 20px;
+  h3 {
+    font-weight: 400;
+  }
+  .client-forms-description-content {
+    border-top: 1px solid rgb(226, 229, 231);
+    p {
+      margin-top: 20px;
+      font-size: 14px;
+    }
+    small {
+      font-size: 12px;
+      color: rgba(0, 0, 0, 0.38);
+    }
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.spec.ts b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.spec.ts
new file mode 100644
index 0000000000..a3a519748a
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ClientFormsComponent } from './forms.component';
+
+describe('ClientFormsComponent', () => {
+  let component: ClientFormsComponent;
+  let fixture: ComponentFixture<ClientFormsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ClientFormsComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ClientFormsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.ts b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.ts
new file mode 100644
index 0000000000..a928e45b89
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/clients/client/forms/forms.component.ts
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+import { Component } from '@angular/core';
+import { ActivatedRoute } from "@angular/router";
+
+@Component({
+  selector: 'app-client-forms',
+  templateUrl: './forms.component.html',
+  styleUrls: ['./forms.component.scss']
+})
+export class ClientFormsComponent {
+  forms: any[];
+  domain: any;
+  client: any;
+
+  constructor(private route: ActivatedRoute) {
+  }
+
+  ngOnInit() {
+    this.domain = this.route.snapshot.data['domain'];
+    this.client = this.route.snapshot.parent.data['client'];
+    this.forms = this.getForms();
+  }
+
+  getForms() {
+    return [
+      {
+        'name': 'Login',
+        'description': 'Login page to authenticate users',
+        'template': 'LOGIN',
+        'enabled': this.clientSettingsValid()
+      },
+      {
+        'name': 'Registration',
+        'description': 'Registration page to create an account',
+        'template': 'REGISTRATION',
+        'enabled': this.clientSettingsValid() && this.allowRegister()
+      },
+      {
+        'name': 'Registration confirmation',
+        'description': 'Register page to confirm user account',
+        'template': 'REGISTRATION_CONFIRMATION',
+        'enabled': this.clientSettingsValid()
+      },
+      {
+        'name': 'Forgot password',
+        'description': 'Forgot password to recover account',
+        'template': 'FORGOT_PASSWORD',
+        'enabled': this.clientSettingsValid() && this.allowResetPassword()
+      },
+      {
+        'name': 'Reset password',
+        'description': 'Reset password page to make a new password',
+        'template': 'RESET_PASSWORD',
+        'enabled': this.clientSettingsValid() && this.allowResetPassword()
+      },
+      {
+        'name': 'User consent',
+        'description': 'User consent to acknowledge and accept data access',
+        'template': 'OAUTH2_USER_CONSENT',
+        'enabled': this.clientSettingsValid()
+      }
+    ]
+  }
+
+  clientSettingsValid() {
+    let authorizedGrantTypes: string[] = this.client.authorizedGrantTypes
+    return authorizedGrantTypes && (authorizedGrantTypes.includes('authorization_code') || authorizedGrantTypes.includes("implicit"));
+  }
+
+  allowRegister() {
+    return this.domain.loginSettings && this.domain.loginSettings.registerEnabled;
+  }
+
+  allowResetPassword() {
+    return this.domain.loginSettings && this.domain.loginSettings.forgotPasswordEnabled;
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/components/clients/select-clients.component.ts b/gravitee-am-ui/src/app/domain/components/clients/select-clients.component.ts
index c2a0c7248d..3285e199c9 100644
--- a/gravitee-am-ui/src/app/domain/components/clients/select-clients.component.ts
+++ b/gravitee-am-ui/src/app/domain/components/clients/select-clients.component.ts
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
-import {ActivatedRoute} from "@angular/router";
-import {FormControl} from "@angular/forms";
-import {ClientService} from "../../../services/client.service";
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { ActivatedRoute } from "@angular/router";
+import { FormControl } from "@angular/forms";
+import { ClientService } from "../../../services/client.service";
 
 @Component({
   selector: 'app-select-clients',
@@ -39,8 +39,8 @@ export class SelectClientsComponent implements OnInit {
     this.clientCtrl.valueChanges
       .subscribe(searchTerm => {
         if (typeof(searchTerm) === 'string' || searchTerm instanceof String) {
-          this.clientService.findByDomain(this.domainId).map(res => res.json()).subscribe(response => {
-            this.filteredClients = response.filter(client => client.clientId.indexOf(searchTerm) === 0);
+          this.clientService.search(this.domainId, searchTerm).map(res => res.json()).subscribe(response => {
+            this.filteredClients = response;
           });
         }
       });
diff --git a/gravitee-am-ui/src/app/domain/components/emails/email/email.component.ts b/gravitee-am-ui/src/app/domain/components/emails/email/email.component.ts
index 8875c30770..e5f310fafe 100644
--- a/gravitee-am-ui/src/app/domain/components/emails/email/email.component.ts
+++ b/gravitee-am-ui/src/app/domain/components/emails/email/email.component.ts
@@ -145,7 +145,9 @@ export class EmailComponent implements OnInit, AfterViewInit {
             this.email.template = this.route.snapshot.queryParams['template'];
             this.email.expiresAfter = 86400;
             this.emailContent =  (' ' + this.defaultEmailContent).slice(1);
+            this.originalEmailContent = (' ' + this.emailContent).slice(1);
             this.emailFound = false;
+            this.formChanged = false;
             this.enableCodeMirror();
           });
         }
diff --git a/gravitee-am-ui/src/app/domain/components/emails/emails.component.html b/gravitee-am-ui/src/app/domain/components/emails/emails.component.html
index 2f8b736069..343866537b 100644
--- a/gravitee-am-ui/src/app/domain/components/emails/emails.component.html
+++ b/gravitee-am-ui/src/app/domain/components/emails/emails.component.html
@@ -15,8 +15,15 @@
     limitations under the License.
 
 -->
-<div class="emails-content">
-  <ngx-datatable class="material" [columnMode]="'flex'" [headerHeight]="40" [footerHeight]="40" [rowHeight]="50" [rows]='emails'>
+<div class="emails-content" *ngIf="!isEmpty()">
+  <ngx-datatable class="material"
+                 [columnMode]="'flex'"
+                 [headerHeight]="40"
+                 [footerHeight]="40"
+                 [rowHeight]="50"
+                 [rows]='emails'
+                 [sorts]="[{prop: 'enabled', dir: 'desc'}]"
+                 [rowClass]="getRowClass">
     <ngx-datatable-column name="Name" [flexGrow]="1">
       <ng-template let-row="row" ngx-datatable-cell-template>
         {{row.name}}
@@ -30,7 +37,8 @@
     <ngx-datatable-column name="Actions" [flexGrow]="1">
       <ng-template let-row="row" ngx-datatable-cell-template>
         <div fxLayout="row" class="emails-actions">
-          <a mat-icon-button [routerLink]="['email']" [queryParams]="{template: row.template}"><mat-icon matTooltip="Settings">settings</mat-icon></a>
+          <mat-icon *ngIf="!row.enabled" matTooltip="Domain settings {{clientId ? 'or client type ' : ''}} do not allow '{{row.name}}' email template customisation">block</mat-icon>
+          <a  *ngIf="row.enabled" mat-icon-button [routerLink]="['email']" [queryParams]="{template: row.template}"><mat-icon matTooltip="Edit email template">edit</mat-icon></a>
         </div>
       </ng-template>
     </ngx-datatable-column>
diff --git a/gravitee-am-ui/src/app/domain/components/emails/emails.component.scss b/gravitee-am-ui/src/app/domain/components/emails/emails.component.scss
index 04bc04ab2e..cd2381185c 100644
--- a/gravitee-am-ui/src/app/domain/components/emails/emails.component.scss
+++ b/gravitee-am-ui/src/app/domain/components/emails/emails.component.scss
@@ -1,3 +1,15 @@
+:host {
+  /deep/ .row-disabled {
+    background: #f1f1f1;
+    border: 1px solid #fafafa;
+    datatable-body-cell {
+      color: grey !important;
+      font-size: 85% !important;
+      font-style: italic !important;
+    }
+  }
+}
+
 .emails-content {
   ngx-datatable {
     font-size: 14px;
diff --git a/gravitee-am-ui/src/app/domain/components/emails/emails.component.ts b/gravitee-am-ui/src/app/domain/components/emails/emails.component.ts
index c9ffae6516..c485aeef57 100644
--- a/gravitee-am-ui/src/app/domain/components/emails/emails.component.ts
+++ b/gravitee-am-ui/src/app/domain/components/emails/emails.component.ts
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
 
 @Component({
   selector: 'app-emails',
@@ -21,28 +22,22 @@ import { Component, OnInit } from '@angular/core';
   styleUrls: ['./emails.component.scss']
 })
 export class EmailsComponent implements OnInit {
-  emails: any[];
-  domainId: string;
-
-  constructor() { }
+  clientId: string;
+  @Input() emails: any[];
 
+  constructor(private route: ActivatedRoute) { }
 
   ngOnInit() {
-    this.emails = this.getEmails();
+    this.clientId = this.route.snapshot.parent.params['clientId'];
+  }
+
+  isEmpty() {
+    return !this.emails || this.emails.length == 0;
   }
 
-  getEmails() {
-    return [
-      {
-        'name': 'Registration confirmation',
-        'description': 'Registration email to confirm user account',
-        'template': 'REGISTRATION_CONFIRMATION'
-      },
-      {
-        'name': 'Reset password',
-        'description': 'Reset password email to ask for a new one',
-        'template': 'RESET_PASSWORD'
-      }
-    ]
+  getRowClass(row) {
+    return {
+      'row-disabled': !row.enabled
+    };
   }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/form/dialog/form-info.component.html b/gravitee-am-ui/src/app/domain/components/forms/form/dialog/form-info.component.html
similarity index 98%
rename from gravitee-am-ui/src/app/domain/settings/forms/form/dialog/form-info.component.html
rename to gravitee-am-ui/src/app/domain/components/forms/form/dialog/form-info.component.html
index 50df776e44..67c1e5817b 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/form/dialog/form-info.component.html
+++ b/gravitee-am-ui/src/app/domain/components/forms/form/dialog/form-info.component.html
@@ -141,6 +141,7 @@
             </div>
             <form th:action="@{resetPassword}" method="post">
               <input type="password" id="password" name="password" required/>
+              <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
               <input type="hidden" name="token" id="token" th:value="${token}"/>
               <button type="submit">Reset password</button>
             </form>
@@ -179,6 +180,7 @@
             </div>
             <form th:action="@{confirmRegistration}" method="post">
               <input type="password" id="password" name="password" required/>
+              <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
               <input type="hidden" name="token" id="token" th:value="${token}"/>
               <button type="submit">Confirm registration</button>
             </form>
diff --git a/gravitee-am-ui/src/app/domain/components/forms/form/form.component.html b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.html
new file mode 100644
index 0000000000..2c88833c23
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.html
@@ -0,0 +1,48 @@
+<!--
+
+    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.
+
+-->
+<div class="form-container">
+  <h1>{{formName}}</h1>
+  <div fxLayout="column">
+    <div>
+      <mat-slide-toggle matTooltip="{{isEnabled() ? 'Disable custom form ?' : 'Enable custom form ?'}}"
+                        [matTooltipPosition]="'after'"
+                        (change)="enableForm($event)"
+                        [checked]="isEnabled()">
+      </mat-slide-toggle>
+      <mat-hint><small class="hint">Create a custom {{template}} form.<button mat-icon-button (click)="openDialog()"><mat-icon>info_outline</mat-icon></button></small></mat-hint>
+    </div>
+  </div>
+
+  <mat-tab-group (selectChange)="onTabSelectedChanged($event);">
+    <mat-tab label="Html" [disabled]="!isEnabled()">
+      <div style= "position:relative;">
+        <codemirror #editor [(ngModel)]="formContent" [config]="config" (change)="onContentChanges($event)"></codemirror>
+      </div>
+    </mat-tab>
+    <mat-tab label="Preview" [disabled]="!isEnabled()">
+      <iframe frameborder="0" class="preview" #preview width="100%" (load)="resizeIframe()">
+      </iframe>
+    </mat-tab>
+  </mat-tab-group>
+  <div style="margin-top: 50px;">
+    <button *ngIf="!formFound" mat-raised-button [disabled]="!formChanged" (click)="create()">SAVE</button>
+    <button *ngIf="formFound" mat-raised-button [disabled]="!formChanged" (click)="update()">UPDATE</button>
+    <span fxFlex></span>
+    <button *ngIf="formFound" mat-raised-button color="warn" (click)="delete($event)">DELETE</button>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/domain/components/forms/form/form.component.scss b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.scss
new file mode 100644
index 0000000000..8a52a56131
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.scss
@@ -0,0 +1,17 @@
+.form-container {
+  h1 {
+    font-weight: 400;
+  }
+}
+
+.hint {
+  color: rgba(0, 0, 0, 0.38);
+}
+
+h4.disabled {
+  color: rgba(0, 0, 0, 0.38)
+}
+
+.preview {
+  margin-top: 10px;
+}
diff --git a/gravitee-am-ui/src/app/domain/components/forms/form/form.component.spec.ts b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.spec.ts
new file mode 100644
index 0000000000..b611d5bfe7
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FormComponent } from './form.component';
+
+describe('FormComponent', () => {
+  let component: FormComponent;
+  let fixture: ComponentFixture<FormComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ FormComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/domain/components/forms/form/form.component.ts b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.ts
new file mode 100644
index 0000000000..a22208d502
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/form/form.component.ts
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core";
+import { ActivatedRoute, Router } from "@angular/router";
+import { BreadcrumbService } from "../../../../../libraries/ng2-breadcrumb/components/breadcrumbService";
+import { AppConfig } from "../../../../../config/app.config";
+import { FormService } from "../../../../services/form.service";
+import { SnackbarService } from "../../../../services/snackbar.service";
+import { DialogService } from "../../../../services/dialog.service";
+import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from "@angular/material";
+
+export interface DialogData {
+  rawTemplate: string;
+  template: string;
+}
+
+@Component({
+  selector: 'app-form',
+  templateUrl: './form.component.html',
+  styleUrls: ['./form.component.scss']
+})
+export class FormComponent implements OnInit, AfterViewInit {
+  private domainId: string;
+  private clientId: string;
+  private defaultFormContent: string = `// Custom form...`;
+  template: string;
+  rawTemplate: string;
+  form: any;
+  formName: string;
+  formContent: string = (' ' + this.defaultFormContent).slice(1);
+  originalFormContent: string = (' ' + this.formContent).slice(1);
+  formFound: boolean = false;
+  formChanged: boolean = false;
+  config: any = { lineNumbers: true, readOnly: true};
+  @ViewChild('editor') editor: any;
+  @ViewChild('preview') preview: ElementRef;
+
+  constructor(private router: Router,
+              private route: ActivatedRoute,
+              private breadcrumbService: BreadcrumbService,
+              private formService: FormService,
+              private snackbarService: SnackbarService,
+              private dialogService: DialogService,
+              public dialog: MatDialog) { }
+
+  ngOnInit() {
+    this.domainId = this.route.snapshot.parent.parent.params['domainId'];
+    this.clientId = this.route.snapshot.parent.params['clientId'];
+    if (this.router.routerState.snapshot.url.startsWith('/settings')) {
+      this.domainId = AppConfig.settings.authentication.domainId;
+      this.rawTemplate = 'LOGIN';
+    } else {
+      this.rawTemplate = this.route.snapshot.queryParams['template'];
+    }
+
+    this.form = this.route.snapshot.data['form'];
+    if (this.form && this.form.content) {
+      this.formContent = this.form.content;
+      this.originalFormContent = (' ' + this.formContent).slice(1);
+      this.formFound = true;
+    } else {
+      this.form = {};
+      this.form.template = this.rawTemplate
+    }
+
+    this.template = this.rawTemplate.toLowerCase().replace(/_/g, ' ');;
+    this.formName = this.template.charAt(0).toUpperCase() + this.template.slice(1);
+    this.initBreadcrumb();
+  }
+
+  ngAfterViewInit(): void {
+    this.enableCodeMirror();
+  }
+
+  initBreadcrumb() {
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/settings/forms/form*', this.template);
+  }
+
+  isEnabled() {
+    return this.form && this.form.enabled;
+  }
+
+  enableForm(event) {
+    this.form.enabled = event.checked;
+    this.enableCodeMirror();
+    this.formChanged = true;
+  }
+
+  onTabSelectedChanged(e) {
+    if (e.index === 1) {
+      this.refreshPreview();
+    }
+  }
+
+  refreshPreview() {
+    let doc =  this.preview.nativeElement.contentDocument || this.preview.nativeElement.contentWindow;
+    doc.open();
+    doc.write(this.formContent);
+    doc.close();
+  }
+
+  onContentChanges(e) {
+    if (e !== this.originalFormContent) {
+      this.formChanged = true;
+    }
+  }
+
+  resizeIframe() {
+    this.preview.nativeElement.style.height = this.preview.nativeElement.contentWindow.document.body.scrollHeight + 'px';
+  }
+
+  create() {
+    this.form['content'] = this.formContent;
+    this.formService.create(this.domainId, this.clientId, this.form).map(res => res.json()).subscribe(data => {
+      this.snackbarService.open("Form created");
+      this.formFound = true;
+      this.form = data;
+      this.formChanged = false;
+    })
+  }
+
+  update() {
+    this.form['content'] = this.formContent;
+    this.formService.update(this.domainId, this.clientId, this.form.id, this.form).map(res => res.json()).subscribe(data => {
+      this.snackbarService.open("Form updated");
+      this.formFound = true;
+      this.form = data;
+      this.formChanged = false;
+    })
+  }
+
+  delete(event) {
+    event.preventDefault();
+    this.dialogService
+      .confirm('Delete form', 'Are you sure you want to delete this form ?')
+      .subscribe(res => {
+        if (res) {
+          this.formService.delete(this.domainId, this.clientId, this.form.id).subscribe(response => {
+            this.snackbarService.open("Form deleted");
+            this.form = {};
+            this.form.template = this.route.snapshot.queryParams['template'];
+            this.formContent =  (' ' + this.defaultFormContent).slice(1);
+            this.originalFormContent = (' ' + this.formContent).slice(1);
+            this.formFound = false;
+            this.formChanged = false;
+            this.enableCodeMirror();
+          });
+        }
+      });
+  }
+
+  openDialog() {
+    this.dialog.open(FormInfoDialog, {
+      data: {rawTemplate: this.rawTemplate, template: this.template}
+    });
+  }
+
+  private enableCodeMirror(): void {
+    this.editor.instance.setOption('readOnly', !this.form.enabled);
+  }
+}
+
+@Component({
+  selector: 'form-info-dialog',
+  templateUrl: './dialog/form-info.component.html',
+})
+export class FormInfoDialog {
+  constructor(public dialogRef: MatDialogRef<FormInfoDialog>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
+}
diff --git a/gravitee-am-ui/src/app/domain/components/forms/forms.component.html b/gravitee-am-ui/src/app/domain/components/forms/forms.component.html
new file mode 100644
index 0000000000..9fda2bbd49
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/forms.component.html
@@ -0,0 +1,46 @@
+<!--
+
+    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.
+
+-->
+<div class="forms-content" *ngIf="!isEmpty()">
+  <ngx-datatable class="material"
+                 [columnMode]="'flex'"
+                 [headerHeight]="40"
+                 [footerHeight]="40"
+                 [rowHeight]="50"
+                 [rows]='forms'
+                 [sorts]="[{prop: 'enabled', dir: 'desc'}]"
+                 [rowClass]="getRowClass">
+    <ngx-datatable-column name="Name" [flexGrow]="1">
+      <ng-template let-row="row" ngx-datatable-cell-template>
+        {{row.name}}
+      </ng-template>
+    </ngx-datatable-column>
+    <ngx-datatable-column name="Description" [flexGrow]="1">
+      <ng-template let-row="row" ngx-datatable-cell-template>
+        {{row.description}}
+      </ng-template>
+    </ngx-datatable-column>
+    <ngx-datatable-column name="Actions" [flexGrow]="1">
+      <ng-template let-row="row" ngx-datatable-cell-template>
+        <div fxLayout="row" class="forms-actions">
+          <mat-icon *ngIf="!row.enabled" matTooltip="Domain settings {{clientId ? 'or client type ' : ''}} do not allow '{{row.name}}' template customisation">block</mat-icon>
+          <a *ngIf="row.enabled" mat-icon-button [routerLink]="['form']" [queryParams]="{template: row.template}"><mat-icon matTooltip="Edit template">edit</mat-icon></a>
+        </div>
+      </ng-template>
+    </ngx-datatable-column>
+  </ngx-datatable>
+</div>
diff --git a/gravitee-am-ui/src/app/domain/components/forms/forms.component.scss b/gravitee-am-ui/src/app/domain/components/forms/forms.component.scss
new file mode 100644
index 0000000000..e3bbfca4e5
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/forms.component.scss
@@ -0,0 +1,31 @@
+:host {
+  /deep/ .row-disabled {
+    background: #f1f1f1;
+    border: 1px solid #fafafa;
+    datatable-body-cell {
+      color: grey !important;
+      font-size: 85% !important;
+      font-style: italic !important;
+    }
+  }
+}
+
+.forms-content {
+  ngx-datatable {
+    font-size: 14px;
+    box-shadow: none;
+
+    .row-disabled {
+      background: #f1f1f1;
+    }
+  }
+}
+
+.forms-actions {
+  margin-left: -10px;
+
+  button, a {
+    top: -9px;
+    color: grey;
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/components/forms/forms.component.spec.ts b/gravitee-am-ui/src/app/domain/components/forms/forms.component.spec.ts
new file mode 100644
index 0000000000..041d1c5d3b
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/forms.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FormsComponent } from './forms.component';
+
+describe('FormsComponent', () => {
+  let component: FormsComponent;
+  let fixture: ComponentFixture<FormsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ FormsComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FormsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/domain/components/forms/forms.component.ts b/gravitee-am-ui/src/app/domain/components/forms/forms.component.ts
new file mode 100644
index 0000000000..e1db88bf7e
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/components/forms/forms.component.ts
@@ -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.
+ */
+import { Component, Input, OnInit } from '@angular/core';
+import { ActivatedRoute } from "@angular/router";
+
+@Component({
+  selector: 'app-forms',
+  templateUrl: './forms.component.html',
+  styleUrls: ['./forms.component.scss']
+})
+export class FormsComponent implements OnInit {
+  @Input() forms: any[];
+  clientId: string;
+
+  constructor(private route: ActivatedRoute) { }
+
+  ngOnInit() {
+    this.clientId = this.route.snapshot.parent.params['clientId'];
+  }
+
+  isEmpty() {
+    return !this.forms || this.forms.length == 0;
+  }
+
+  getRowClass(row) {
+    return {
+      'row-disabled': !row.enabled
+    };
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/settings/emails/emails.component.html b/gravitee-am-ui/src/app/domain/settings/emails/emails.component.html
index 8bafcde9ca..05f4c29de4 100644
--- a/gravitee-am-ui/src/app/domain/settings/emails/emails.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/emails/emails.component.html
@@ -17,5 +17,5 @@
 -->
 <div class="emails-container">
   <h1>Emails</h1>
-  <app-emails></app-emails>
+  <app-emails [emails]="emails"></app-emails>
 </div>
diff --git a/gravitee-am-ui/src/app/domain/settings/emails/emails.component.ts b/gravitee-am-ui/src/app/domain/settings/emails/emails.component.ts
index 516d02d6b6..50cc798ef1 100644
--- a/gravitee-am-ui/src/app/domain/settings/emails/emails.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/emails/emails.component.ts
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 import { Component } from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
 
 @Component({
   selector: 'app-domain-emails',
@@ -21,6 +22,34 @@ import { Component } from '@angular/core';
   styleUrls: ['./emails.component.scss']
 })
 export class DomainSettingsEmailsComponent {
+  domain: any;
+  emails: any[];
 
-  constructor() { }
+  constructor(private route: ActivatedRoute) { }
+
+  ngOnInit() {
+    this.domain = this.route.snapshot.data['domain'];
+    this.emails = this.getEmails();
+  }
+
+  getEmails() {
+    return [
+      {
+        'name': 'Registration confirmation',
+        'description': 'Registration email to confirm user account',
+        'template': 'REGISTRATION_CONFIRMATION',
+        'enabled': true
+      },
+      {
+        'name': 'Reset password',
+        'description': 'Reset password email to ask for a new one',
+        'template': 'RESET_PASSWORD',
+        'enabled': this.allowResetPassword()
+      }
+    ]
+  }
+
+  allowResetPassword() {
+    return this.domain.loginSettings && this.domain.loginSettings.forgotPasswordEnabled;
+  }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.html b/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.html
index e0d72306c9..b3a84879bf 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.html
@@ -16,33 +16,5 @@
 
 -->
 <div class="domain-form-container">
-  <h1>{{formName}}</h1>
-  <div fxLayout="column">
-    <div>
-      <mat-slide-toggle matTooltip="{{isEnabled() ? 'Disable custom form ?' : 'Enable custom form ?'}}"
-                        [matTooltipPosition]="'after'"
-                        (change)="enableForm($event)"
-                        [checked]="isEnabled()">
-      </mat-slide-toggle>
-      <mat-hint><small class="hint">Create a custom {{template}} form.<button mat-icon-button (click)="openDialog()"><mat-icon>info_outline</mat-icon></button></small></mat-hint>
-    </div>
-  </div>
-
-  <mat-tab-group (selectChange)="onTabSelectedChanged($event);">
-    <mat-tab label="Html" [disabled]="!isEnabled()">
-      <div style= "position:relative;">
-        <codemirror #editor [(ngModel)]="formContent" [config]="config" (change)="onContentChanges($event)"></codemirror>
-      </div>
-    </mat-tab>
-    <mat-tab label="Preview" [disabled]="!isEnabled()">
-      <iframe frameborder="0" class="preview" #preview width="100%" (load)="resizeIframe()">
-      </iframe>
-    </mat-tab>
-  </mat-tab-group>
-  <div style="margin-top: 50px;">
-    <button *ngIf="!formFound" mat-raised-button [disabled]="!formChanged" (click)="create()">SAVE</button>
-    <button *ngIf="formFound" mat-raised-button [disabled]="!formChanged" (click)="update()">UPDATE</button>
-    <span fxFlex></span>
-    <button *ngIf="formFound" mat-raised-button color="warn" (click)="delete($event)">DELETE</button>
-  </div>
+  <app-form></app-form>
 </div>
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.scss b/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.scss
index 1536118943..a626587762 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.scss
+++ b/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.scss
@@ -1,18 +1,3 @@
 .domain-form-container {
   padding: 20px 70px 20px 50px;
-  h1 {
-    font-weight: 400;
-  }
-}
-
-.hint {
-  color: rgba(0, 0, 0, 0.38);
-}
-
-h4.disabled {
-  color: rgba(0, 0, 0, 0.38)
-}
-
-.preview {
-  margin-top: 10px;
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.ts b/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.ts
index 53ae63b32e..5478a4affc 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/forms/form/form.component.ts
@@ -13,47 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core";
+import { Component, OnInit } from "@angular/core";
 import { ActivatedRoute, Router } from "@angular/router";
 import { BreadcrumbService } from "../../../../../libraries/ng2-breadcrumb/components/breadcrumbService";
 import { AppConfig } from "../../../../../config/app.config";
-import { FormService } from "../../../../services/form.service";
-import { SnackbarService } from "../../../../services/snackbar.service";
-import { DialogService } from "../../../../services/dialog.service";
-import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from "@angular/material";
-
-export interface DialogData {
-  rawTemplate: string;
-  template: string;
-}
 
 @Component({
-  selector: 'app-form',
+  selector: 'app-domain-form',
   templateUrl: './form.component.html',
   styleUrls: ['./form.component.scss']
 })
-export class FormComponent implements OnInit, AfterViewInit {
+export class DomainSettingsFormComponent implements OnInit {
   private domainId: string;
-  private defaultFormContent: string = `// Custom form...`;
   template: string;
   rawTemplate: string;
-  form: any;
-  formName: string;
-  formContent: string = (' ' + this.defaultFormContent).slice(1);
-  originalFormContent: string = (' ' + this.formContent).slice(1);
-  formFound: boolean = false;
-  formChanged: boolean = false;
-  config: any = { lineNumbers: true, readOnly: true};
-  @ViewChild('editor') editor: any;
-  @ViewChild('preview') preview: ElementRef;
 
-  constructor(private router: Router,
-              private route: ActivatedRoute,
-              private breadcrumbService: BreadcrumbService,
-              private formService: FormService,
-              private snackbarService: SnackbarService,
-              private dialogService: DialogService,
-              public dialog: MatDialog) { }
+  constructor(private route: ActivatedRoute,
+              private router: Router,
+              private breadcrumbService: BreadcrumbService) {
+  }
 
   ngOnInit() {
     this.domainId = this.route.snapshot.parent.parent.params['domainId'];
@@ -63,116 +41,12 @@ export class FormComponent implements OnInit, AfterViewInit {
     } else {
       this.rawTemplate = this.route.snapshot.queryParams['template'];
     }
-
-    this.form = this.route.snapshot.data['form'];
-    if (this.form && this.form.content) {
-      this.formContent = this.form.content;
-      this.originalFormContent = (' ' + this.originalFormContent).slice(1);
-      this.formFound = true;
-    } else {
-      this.form = {};
-      this.form.template = this.rawTemplate
-    }
-
-    this.template = this.rawTemplate.toLowerCase().replace(/_/g, ' ');;
-    this.formName = this.template.charAt(0).toUpperCase() + this.template.slice(1);
+    this.domainId = this.route.snapshot.parent.parent.params['domainId'];
+    this.template = this.rawTemplate.toLowerCase().replace(/_/g, ' ');
     this.initBreadcrumb();
   }
 
-  ngAfterViewInit(): void {
-    this.enableCodeMirror();
-  }
-
   initBreadcrumb() {
-    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domainId+'/settings/forms/form*', this.template);
-  }
-
-  isEnabled() {
-    return this.form && this.form.enabled;
-  }
-
-  enableForm(event) {
-    this.form.enabled = event.checked;
-    this.enableCodeMirror();
-    this.formChanged = true;
-  }
-
-  onTabSelectedChanged(e) {
-    if (e.index === 1) {
-      this.refreshPreview();
-    }
-  }
-
-  refreshPreview() {
-    let doc =  this.preview.nativeElement.contentDocument || this.preview.nativeElement.contentWindow;
-    doc.open();
-    doc.write(this.formContent);
-    doc.close();
-  }
-
-  onContentChanges(e) {
-    if (e !== this.originalFormContent) {
-      this.formChanged = true;
-    }
-  }
-
-  resizeIframe() {
-    this.preview.nativeElement.style.height = this.preview.nativeElement.contentWindow.document.body.scrollHeight + 'px';
-  }
-
-  create() {
-    this.form['content'] = this.formContent;
-    this.formService.create(this.domainId, this.form).map(res => res.json()).subscribe(data => {
-      this.snackbarService.open("Form created");
-      this.formFound = true;
-      this.form = data;
-      this.formChanged = false;
-    })
-  }
-
-  update() {
-    this.form['content'] = this.formContent;
-    this.formService.update(this.domainId, this.form.id, this.form).map(res => res.json()).subscribe(data => {
-      this.snackbarService.open("Form updated");
-      this.formFound = true;
-      this.form = data;
-      this.formChanged = false;
-    })
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/' + this.domainId + '/settings/forms/form*', this.template);
   }
-
-  delete(event) {
-    event.preventDefault();
-    this.dialogService
-      .confirm('Delete form', 'Are you sure you want to delete this form ?')
-      .subscribe(res => {
-        if (res) {
-          this.formService.delete(this.domainId, this.form.id).subscribe(response => {
-            this.snackbarService.open("Form deleted");
-            this.form = {};
-            this.form.template = this.route.snapshot.queryParams['template'];
-            this.formContent =  (' ' + this.defaultFormContent).slice(1);
-            this.formFound = false;
-            this.enableCodeMirror();
-          });
-        }
-      });
-  }
-
-  openDialog() {
-    this.dialog.open(FormInfoDialog, {
-      data: {rawTemplate: this.rawTemplate, template: this.template}
-    });
-  }
-
-  private enableCodeMirror(): void {
-    this.editor.instance.setOption('readOnly', !this.form.enabled);
-  }
-}
-
-@Component({
-  selector: 'form-info-dialog',
-  templateUrl: './dialog/form-info.component.html',
-})
-export class FormInfoDialog {
-  constructor(public dialogRef: MatDialogRef<FormInfoDialog>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.html b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.html
index 770df237c6..d0303772c9 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.html
@@ -16,26 +16,6 @@
 
 -->
 <div class="forms-container">
-  <h1 *ngIf="!isEmpty">Forms</h1>
-  <div class="forms-content" *ngIf="!isEmpty">
-    <ngx-datatable class="material" [columnMode]="'flex'" [headerHeight]="40" [footerHeight]="40" [rowHeight]="50" [rows]='forms'>
-      <ngx-datatable-column name="Name" [flexGrow]="1">
-        <ng-template let-row="row" ngx-datatable-cell-template>
-          {{row.name}}
-        </ng-template>
-      </ngx-datatable-column>
-      <ngx-datatable-column name="Description" [flexGrow]="1">
-        <ng-template let-row="row" ngx-datatable-cell-template>
-          {{row.description}}
-        </ng-template>
-      </ngx-datatable-column>
-      <ngx-datatable-column name="Actions" [flexGrow]="1">
-        <ng-template let-row="row" ngx-datatable-cell-template>
-          <div fxLayout="row" class="forms-actions">
-            <a mat-icon-button [routerLink]="['form']" [queryParams]="{template: row.template}"><mat-icon matTooltip="Settings">settings</mat-icon></a>
-          </div>
-        </ng-template>
-      </ngx-datatable-column>
-    </ngx-datatable>
-  </div>
+  <h1>Forms</h1>
+  <app-forms [forms]="forms"></app-forms>
 </div>
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.scss b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.scss
index 3def39cb6f..e0e0b6b0e4 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.scss
+++ b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.scss
@@ -3,17 +3,4 @@
   h1 {
     font-weight: 400;
   }
-  ngx-datatable {
-    font-size: 14px;
-    box-shadow: none;
-  }
-}
-
-.forms-actions {
-  margin-left: -10px;
-
-  button, a {
-    top: -9px;
-    color: grey;
-  }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.spec.ts b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.spec.ts
index 9a27610f63..38e03b420f 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.spec.ts
+++ b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.spec.ts
@@ -15,7 +15,7 @@
  */
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { DomainSettingsFormsComponent } from './pages.component';
+import { DomainSettingsFormsComponent } from './forms.component';
 
 describe('DomainSettingsFormsComponent', () => {
   let component: DomainSettingsFormsComponent;
diff --git a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.ts b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.ts
index 3105dd91f9..36de54ba58 100644
--- a/gravitee-am-ui/src/app/domain/settings/forms/forms.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/forms/forms.component.ts
@@ -13,60 +13,71 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
 
 @Component({
   selector: 'app-domain-forms',
   templateUrl: './forms.component.html',
   styleUrls: ['./forms.component.scss']
 })
-export class DomainSettingsFormsComponent implements OnInit {
+export class DomainSettingsFormsComponent {
   forms: any[];
-  domainId: string;
-
-  constructor() { }
+  domain: any;
 
+  constructor(private route: ActivatedRoute) { }
 
   ngOnInit() {
+    this.domain = this.route.snapshot.data['domain'];
     this.forms = this.getForms();
   }
 
-  get isEmpty() {
-    return !this.forms || this.forms.length == 0;
-  }
-
   getForms() {
     return [
       {
         'name': 'Login',
         'description': 'Login page to authenticate users',
-        'template': 'LOGIN'
+        'template': 'LOGIN',
+        'enabled': true
       },
       {
         'name': 'Registration',
         'description': 'Registration page to create an account',
-        'template': 'REGISTRATION'
+        'template': 'REGISTRATION',
+        'enabled': this.allowRegister()
       },
       {
         'name': 'Registration confirmation',
         'description': 'Register page to confirm user account',
-        'template': 'REGISTRATION_CONFIRMATION'
+        'template': 'REGISTRATION_CONFIRMATION',
+        'enabled': true
       },
       {
         'name': 'Forgot password',
         'description': 'Forgot password to recover account',
-        'template': 'FORGOT_PASSWORD'
+        'template': 'FORGOT_PASSWORD',
+        'enabled': this.allowResetPassword()
       },
       {
         'name': 'Reset password',
         'description': 'Reset password page to make a new password',
-        'template': 'RESET_PASSWORD'
+        'template': 'RESET_PASSWORD',
+        'enabled': this.allowResetPassword()
       },
       {
         'name': 'User consent',
         'description': 'User consent to acknowledge and accept data access',
-        'template': 'OAUTH2_USER_CONSENT'
+        'template': 'OAUTH2_USER_CONSENT',
+        'enabled': true
       }
     ]
   }
+
+  allowRegister() {
+    return this.domain.loginSettings && this.domain.loginSettings.registerEnabled;
+  }
+
+  allowResetPassword() {
+    return this.domain.loginSettings && this.domain.loginSettings.forgotPasswordEnabled;
+  }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/users/user/user.component.ts b/gravitee-am-ui/src/app/domain/settings/users/user/user.component.ts
index 557a703c82..cafddc472b 100644
--- a/gravitee-am-ui/src/app/domain/settings/users/user/user.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/users/user/user.component.ts
@@ -165,10 +165,12 @@ export class UserComponent implements OnInit {
 
   onClientSelectionChanged(event) {
     this.user.client = event.id;
+    this.formChanged = true;
   }
 
   onClientDeleted(event) {
     this.user.client = null;
+    this.formChanged = true;
   }
 
   displayClientName() {
diff --git a/gravitee-am-ui/src/app/resolvers/form.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/form.resolver.spec.ts
index 82188b0c84..1eb48f1553 100644
--- a/gravitee-am-ui/src/app/resolvers/form.resolver.spec.ts
+++ b/gravitee-am-ui/src/app/resolvers/form.resolver.spec.ts
@@ -15,9 +15,9 @@
  */
 import { TestBed, inject } from '@angular/core/testing';
 
-import { FormResolver } from './page.resolver';
+import { FormResolver } from './form.resolver';
 
-describe('PageResolver', () => {
+describe('FormResolver', () => {
   beforeEach(() => {
     TestBed.configureTestingModule({
       providers: [FormResolver]
diff --git a/gravitee-am-ui/src/app/resolvers/form.resolver.ts b/gravitee-am-ui/src/app/resolvers/form.resolver.ts
index 165a83746c..eb23419ee5 100644
--- a/gravitee-am-ui/src/app/resolvers/form.resolver.ts
+++ b/gravitee-am-ui/src/app/resolvers/form.resolver.ts
@@ -26,12 +26,13 @@ export class FormResolver implements Resolve<any> {
 
   resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
     let domainId = AppConfig.settings.authentication.domainId;
+    let clientId = route.parent.paramMap.get('clientId');
     let pageTemplate = 'LOGIN';
     if (!state.url.startsWith('/settings')) {
       pageTemplate = route.queryParams['template'];
       domainId = (route.parent.paramMap.get('domainId')) ? route.parent.paramMap.get('domainId') : route.parent.parent.paramMap.get('domainId');
     }
-    return this.formService.get(domainId, pageTemplate)
+    return this.formService.get(domainId, clientId, pageTemplate)
       .map(res => res.json())
       .catch(res => {
         return Observable.of({});
diff --git a/gravitee-am-ui/src/app/services/client.service.ts b/gravitee-am-ui/src/app/services/client.service.ts
index 3e289f474e..1f45f45b4f 100644
--- a/gravitee-am-ui/src/app/services/client.service.ts
+++ b/gravitee-am-ui/src/app/services/client.service.ts
@@ -28,6 +28,10 @@ export class ClientService {
     return this.http.get(this.clientsURL + domainId + "/clients");
   }
 
+  search(domainId, searchTerm) {
+    return this.http.get(this.clientsURL + domainId + "/clients?q=" + searchTerm);
+  }
+
   get(domainId, id): Observable<Response> {
     return this.http.get(this.clientsURL + domainId + "/clients/" + id);
   }
diff --git a/gravitee-am-ui/src/app/services/form.service.ts b/gravitee-am-ui/src/app/services/form.service.ts
index 6e93131cbe..ec25d36d68 100644
--- a/gravitee-am-ui/src/app/services/form.service.ts
+++ b/gravitee-am-ui/src/app/services/form.service.ts
@@ -24,23 +24,23 @@ export class FormService {
 
   constructor(private http: Http) { }
 
-  get(domainId, pageTemplate): Observable<Response>  {
-    return this.http.get(this.formsUrl + domainId + "/forms?template=" + pageTemplate);
+  get(domainId, clientId, formTemplate): Observable<Response>  {
+    return this.http.get(this.formsUrl + domainId + (clientId ? "/clients/" + clientId : "") + "/forms?template=" + formTemplate);
   }
 
-  create(domainId, page): Observable<Response>  {
-    return this.http.post(this.formsUrl + domainId + "/forms", page);
+  create(domainId, clientId, form): Observable<Response>  {
+    return this.http.post(this.formsUrl + domainId + (clientId ? "/clients/" + clientId : "") + "/forms", form);
   }
 
-  update(domainId, id, page): Observable<Response>  {
-    return this.http.put(this.formsUrl + domainId + "/forms/" + id, {
-      'enabled' : page.enabled,
-      'content' : page.content
+  update(domainId, clientId, id, form): Observable<Response>  {
+    return this.http.put(this.formsUrl + domainId + (clientId ? "/clients/" + clientId : "") + "/forms/" + id, {
+      'enabled' : form.enabled,
+      'content' : form.content
     });
   }
 
-  delete(domainId, id): Observable<Response>  {
-    return this.http.delete(this.formsUrl + domainId + "/forms/" + id);
+  delete(domainId, clientId, id): Observable<Response>  {
+    return this.http.delete(this.formsUrl + domainId + (clientId ? "/clients/" + clientId : "") + "/forms/" + id);
   }
 
 }
diff --git a/gravitee-am-ui/src/app/services/user.service.ts b/gravitee-am-ui/src/app/services/user.service.ts
index e878880dee..0ac3644db6 100644
--- a/gravitee-am-ui/src/app/services/user.service.ts
+++ b/gravitee-am-ui/src/app/services/user.service.ts
@@ -42,6 +42,7 @@ export class UserService {
       'lastName' : user.lastName,
       'email' : user.email,
       'enabled': user.enabled,
+      'client' : user.client,
       'additionalInformation' : user.additionalInformation
     });
   }


Test patch:
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
index 19f1d97473..7adbc54447 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
@@ -94,6 +94,9 @@ public abstract class JerseySpringTest {
     @Autowired
     protected EmailManager emailManager;
 
+    @Autowired
+    protected FormService formService;
+
     @Configuration
     @ComponentScan("io.gravitee.am.management.handlers.management.api.resources.enhancer")
     static class ContextConfiguration {
@@ -181,6 +184,11 @@ public abstract class JerseySpringTest {
         public EmailManager emailManager() {
             return mock(EmailManager.class);
         }
+
+        @Bean
+        public FormService formService() {
+            return mock(FormService.class);
+        }
     }
 
     private JerseyTest _jerseyTest;
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FormResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FormResourceTest.java
new file mode 100644
index 0000000000..1097e17afa
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FormResourceTest.java
@@ -0,0 +1,101 @@
+/**
+ * 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.handlers.management.api.JerseySpringTest;
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Email;
+import io.gravitee.am.model.Form;
+import io.gravitee.am.service.exception.FormNotFoundException;
+import io.gravitee.am.service.model.UpdateForm;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import org.junit.Test;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FormResourceTest extends JerseySpringTest {
+
+    @Test
+    public void shouldUpdate() {
+        final String formId = "form-1";
+        final String domainId = "domain-1";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        UpdateForm updateForm = new UpdateForm();
+        updateForm.setContent("content");
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Single.just(new Form())).when(formService).update(eq(domainId), eq(formId), any());
+
+        final Response response = target("domains")
+                .path(domainId)
+                .path("forms")
+                .path(formId)
+                .request().put(Entity.json(updateForm));
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void shouldDelete() {
+        final String formId = "form-1";
+        final String domainId = "domain-1";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Completable.complete()).when(formService).delete(eq(formId));
+
+        final Response response = target("domains")
+                .path(domainId)
+                .path("forms")
+                .path(formId)
+                .request().delete();
+        assertEquals(HttpStatusCode.NO_CONTENT_204, response.getStatus());
+    }
+
+    @Test
+    public void shouldDelete_formNotFound() {
+        final String formId = "form-1";
+        final String domainId = "domain-1";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Completable.error(new FormNotFoundException(formId))).when(formService).delete(eq(formId));
+
+        final Response response = target("domains")
+                .path(domainId)
+                .path("forms")
+                .path(formId)
+                .request().delete();
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FormsResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FormsResourceTest.java
new file mode 100644
index 0000000000..d50591ed48
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FormsResourceTest.java
@@ -0,0 +1,101 @@
+/**
+ * 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.handlers.management.api.JerseySpringTest;
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Email;
+import io.gravitee.am.model.Form;
+import io.gravitee.am.model.Template;
+import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.model.NewForm;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import org.junit.Test;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FormsResourceTest extends JerseySpringTest {
+
+    @Test
+    public void shouldGetForm() {
+        final String domainId = "domain-1";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        final Form mockForm = new Form();
+        mockForm.setId("form-1-id");
+        mockForm.setTemplate(Template.LOGIN.template());
+        mockForm.setDomain(domainId);
+
+        doReturn(Maybe.just(mockForm)).when(formService).findByDomainAndTemplate(domainId, Template.LOGIN.template());
+
+        final Response response = target("domains")
+                .path(domainId).path("forms")
+                .queryParam("template", Template.LOGIN)
+                .request()
+                .get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Email responseEntity = response.readEntity(Email.class);
+        assertTrue(responseEntity.getId().equals("form-1-id"));
+    }
+
+    @Test
+    public void shouldGetForms_technicalManagementException() {
+        final String domainId = "domain-1";
+        doReturn(Maybe.error(new TechnicalManagementException("error occurs"))).when(formService).findByDomainAndTemplate(domainId, Template.LOGIN.template());
+
+        final Response response = target("domains")
+                .path(domainId).path("forms")
+                .queryParam("template", Template.LOGIN)
+                .request()
+                .get();
+        assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR_500, response.getStatus());
+    }
+
+    @Test
+    public void shouldCreate() {
+        final String domainId = "domain-1";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        NewForm newForm = new NewForm();
+        newForm.setTemplate(Template.LOGIN);
+        newForm.setContent("content");
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Single.just(new Form())).when(formService).create(eq(domainId), any());
+
+        final Response response = target("domains")
+                .path(domainId)
+                .path("forms")
+                .request().post(Entity.json(newForm));
+        assertEquals(HttpStatusCode.CREATED_201, response.getStatus());
+    }
+}
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/ClientServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/ClientServiceTest.java
index ace746b3d4..3225e9819a 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/ClientServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/ClientServiceTest.java
@@ -15,17 +15,11 @@
  */
 package io.gravitee.am.service;
 
-import io.gravitee.am.model.Client;
-import io.gravitee.am.model.Domain;
-import io.gravitee.am.model.IdentityProvider;
+import io.gravitee.am.model.*;
 import io.gravitee.am.model.common.Page;
 import io.gravitee.am.repository.exceptions.TechnicalException;
 import io.gravitee.am.repository.management.api.ClientRepository;
-import io.gravitee.am.service.exception.ClientAlreadyExistsException;
-import io.gravitee.am.service.exception.ClientNotFoundException;
-import io.gravitee.am.service.exception.InvalidClientMetadataException;
-import io.gravitee.am.service.exception.InvalidRedirectUriException;
-import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.exception.*;
 import io.gravitee.am.service.impl.ClientServiceImpl;
 import io.gravitee.am.service.model.NewClient;
 import io.gravitee.am.service.model.PatchClient;
@@ -44,14 +38,10 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 
-import static org.mockito.Matchers.*;
-import static org.mockito.Mockito.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.*;
 
 /**
@@ -77,6 +67,12 @@ public class ClientServiceTest {
     @Mock
     private ClientRepository clientRepository;
 
+    @Mock
+    private FormService formService;
+
+    @Mock
+    private EmailTemplateService emailTemplateService;
+
     private final static String DOMAIN = "domain1";
 
     @Test
@@ -582,6 +578,31 @@ public class ClientServiceTest {
         when(clientRepository.findById("my-client")).thenReturn(Maybe.just(existingClient));
         when(clientRepository.delete("my-client")).thenReturn(Completable.complete());
         when(domainService.reload(eq("my-domain"), any())).thenReturn(Single.just(new Domain()));
+        when(formService.findByDomain("my-domain")).thenReturn(Single.just(Collections.singletonList(new Form())));
+        when(formService.delete(anyString())).thenReturn(Completable.complete());
+        when(emailTemplateService.findByDomain("my-domain")).thenReturn(Single.just(Collections.singletonList(new Email())));
+        when(emailTemplateService.delete(anyString())).thenReturn(Completable.complete());
+
+        TestObserver testObserver = clientService.delete("my-client").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+
+        verify(clientRepository, times(1)).delete("my-client");
+        verify(formService, times(1)).delete(anyString());
+        verify(emailTemplateService, times(1)).delete(anyString());
+    }
+
+    @Test
+    public void shouldDelete_withoutRelatedData() {
+        Client existingClient = Mockito.mock(Client.class);
+        when(existingClient.getDomain()).thenReturn("my-domain");
+        when(clientRepository.findById("my-client")).thenReturn(Maybe.just(existingClient));
+        when(clientRepository.delete("my-client")).thenReturn(Completable.complete());
+        when(domainService.reload(eq("my-domain"), any())).thenReturn(Single.just(new Domain()));
+        when(formService.findByDomain("my-domain")).thenReturn(Single.just(Collections.emptyList()));
+        when(emailTemplateService.findByDomain("my-domain")).thenReturn(Single.just(Collections.emptyList()));
 
         TestObserver testObserver = clientService.delete("my-client").test();
         testObserver.awaitTerminalEvent();
@@ -590,6 +611,8 @@ public class ClientServiceTest {
         testObserver.assertNoErrors();
 
         verify(clientRepository, times(1)).delete("my-client");
+        verify(formService, never()).delete(anyString());
+        verify(emailTemplateService, never()).delete(anyString());
     }
 
     @Test
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java
index 416fae670a..dd93a88b89 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java
@@ -64,6 +64,8 @@ public class DomainServiceTest {
     private static final String USER_ID = "id-user";
     private static final String SCOPE_ID = "id-scope";
     private static final String GROUP_ID = "id-group";
+    private static final String FORM_ID = "id-form";
+    private static final String EMAIL_ID = "id-email";
 
     @InjectMocks
     private DomainService domainService = new DomainServiceImpl();
@@ -92,6 +94,12 @@ public class DomainServiceTest {
     @Mock
     private Group group;
 
+    @Mock
+    private Form form;
+
+    @Mock
+    private Email email;
+
     @Mock
     private DomainRepository domainRepository;
 
@@ -119,6 +127,13 @@ public class DomainServiceTest {
     @Mock
     private GroupService groupService;
 
+    @Mock
+    private FormService formService;
+
+    @Mock
+    private EmailTemplateService emailTemplateService;
+
+
     @Test
     public void shouldFindById() {
         when(domainRepository.findById("my-domain")).thenReturn(Maybe.just(new Domain()));
@@ -396,6 +411,12 @@ public class DomainServiceTest {
         when(group.getId()).thenReturn(GROUP_ID);
         when(groupService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.singletonList(group)));
         when(groupService.delete(anyString())).thenReturn(Completable.complete());
+        when(form.getId()).thenReturn(FORM_ID);
+        when(formService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.singletonList(form)));
+        when(formService.delete(anyString())).thenReturn(Completable.complete());
+        when(email.getId()).thenReturn(EMAIL_ID);
+        when(emailTemplateService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.singletonList(email)));
+        when(emailTemplateService.delete(anyString())).thenReturn(Completable.complete());
 
         TestObserver testObserver = domainService.delete(DOMAIN_ID).test();
         testObserver.awaitTerminalEvent();
@@ -411,6 +432,8 @@ public class DomainServiceTest {
         verify(userService, times(1)).delete(USER_ID);
         verify(scopeService, times(1)).delete(SCOPE_ID, true);
         verify(groupService, times(1)).delete(GROUP_ID);
+        verify(formService, times(1)).delete(FORM_ID);
+        verify(emailTemplateService, times(1)).delete(EMAIL_ID);
     }
 
     @Test
@@ -425,6 +448,8 @@ public class DomainServiceTest {
         when(userService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptySet()));
         when(scopeService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptySet()));
         when(groupService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptyList()));
+        when(formService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptyList()));
+        when(emailTemplateService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptyList()));
 
         TestObserver testObserver = domainService.delete(DOMAIN_ID).test();
         testObserver.awaitTerminalEvent();
@@ -439,6 +464,8 @@ public class DomainServiceTest {
         verify(roleService, never()).delete(anyString());
         verify(userService, never()).delete(anyString());
         verify(scopeService, never()).delete(anyString(), anyBoolean());
+        verify(formService, never()).delete(anyString());
+        verify(emailTemplateService, never()).delete(anyString());
     }
 
     @Test