Repository: gravitee-io/graviteeio-access-management
Pull request #624 (2019-07-13 00:33:50)

[Snyk] Fix for 3 vulnerable dependencies
#### Description
This PR fixes one or more vulnerable packages in the `maven` dependencies of this project.
See the [Snyk test report](https://app.snyk.io/org/gravitee.io/test/github/4029ed0d-0154-49e3-9c77-2964501850c1/master..snyk-fix-eca15bc733dc3539014dc58fe03038f0) for more details.

#### Snyk Project: [gravitee-io/graviteeio-access-management:gravitee-am-management-api/gravitee-am-management-api-service/pom.xml](https://app.snyk.io/org/gravitee.io/project/4029ed0d-0154-49e3-9c77-2964501850c1)
#### Snyk Organization: [gravitee.io](https://app.snyk.io/org/gravitee.io)


#### Changes included in this PR

- Changes to the following files to upgrade the vulnerable dependencies to a fixed version:
    - pom.xml


#### Vulnerabilities that will be fixed
##### With an upgrade:
  - `pom.xml`
      - `io.jsonwebtoken:jjwt-jackson@0.10.6 > io.jsonwebtoken:jjwt-jackson@0.10.7` 
          - [Deserialization of Untrusted Data](https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-174736)
          - [Deserialization of Untrusted Data](https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-450207)
          - [Deserialization of Untrusted Data](https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-450917)



You can read more about Snyk's upgrade and patch logic in [Snyk's documentation](https://snyk.io/docs/using-snyk/).

Check the changes in this PR to ensure they won't cause issues with your project.

Stay secure,
The Snyk team

_**Note**: You are seeing this because you or someone else with access to this repository has authorised Snyk to open Fix PRs. To review the settings for this Snyk project please go to the [project settings page](https://app.snyk.io/org/gravitee.io/project/4029ed0d-0154-49e3-9c77-2964501850c1/settings)._

[//]: # (snyk:metadata:{"type":"auto","packageManager":"maven","vulns":["SNYK-JAVA-COMFASTERXMLJACKSONCORE-174736","SNYK-JAVA-COMFASTERXMLJACKSONCORE-450207","SNYK-JAVA-COMFASTERXMLJACKSONCORE-450917"],"patch":[],"upgrade":["SNYK-JAVA-COMFASTERXMLJACKSONCORE-174736","SNYK-JAVA-COMFASTERXMLJACKSONCORE-450207","SNYK-JAVA-COMFASTERXMLJACKSONCORE-450917"],"isBreakingChange":false,"env":"prod","dependencies":[{"name":"io.jsonwebtoken:jjwt-jackson","from":"0.10.6","to":"0.10.7"}],"prType":"fix"})



Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 7627ffb46c..8068d67b39 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -1,5 +1,52 @@
 # Change Log
 
+== https://github.com/gravitee-io/issues/issues/milestone/144?closed=1[AM - 2.8.0 (2019-07-17)]
+
+=== Bug fixes
+
+*_Audits_*
+
+- E11000 duplicate key error  https://github.com/gravitee-io/issues/issues/2368[#2368]
+
+*_Gateway_*
+
+- Management] logback gravitee.am.log.dir_IS_UNDEFINED https://github.com/gravitee-io/issues/issues/2430[#2430]
+
+*_Identityprovider_*
+
+- [Ldap] unable to use complex searchFilter https://github.com/gravitee-io/issues/issues/2428[#2428]
+
+*_Idp_*
+
+- [Role-mapper] invalid grant if group has no member https://github.com/gravitee-io/issues/issues/2073[#2073]
+
+=== Features
+
+*_Dcr_*
+
+- Discovery & default scopes https://github.com/gravitee-io/issues/issues/2326[#2326]
+- Manage templating https://github.com/gravitee-io/issues/issues/2454[#2454]
+
+*_Management_*
+
+- Tokens claims mapper https://github.com/gravitee-io/issues/issues/2409[#2409]
+- Username/Email case-sensitivity https://github.com/gravitee-io/issues/issues/2445[#2445]
+
+=== Improvements
+
+*_Docker_*
+
+- Remove gravitee user for openshift support https://github.com/gravitee-io/issues/issues/2438[#2438]
+
+*_Gateway_*
+
+- Add a X-Transaction-Id when hitting AM Gateway https://github.com/gravitee-io/issues/issues/2450[#2450]
+
+*_Management_*
+
+- Add a warning message if JWT secret is the default one https://github.com/gravitee-io/issues/issues/2451[#2451]
+
+
 == https://github.com/gravitee-io/issues/issues/milestone/148?closed=1[AM - 2.7.1 (2019-06-27)]
 
 === Bug fixes
diff --git a/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml b/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml
index ac9d6538aa..f087710eae 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml
+++ b/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-certificate</artifactId>
         <groupId>io.gravitee.am.certificate</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -30,7 +30,7 @@
     <artifactId>gravitee-am-certificate-api</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Certificate - API</name>
 
     <dependencies>
diff --git a/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml b/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml
index c0cbdd18e2..3752ef5e2f 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml
+++ b/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml
@@ -22,14 +22,14 @@
     <parent>
         <artifactId>gravitee-am-certificate</artifactId>
         <groupId>io.gravitee.am.certificate</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>gravitee-am-certificate-javakeystore</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Certificate - JavaKeyStore</name>
 
     <dependencies>
diff --git a/gravitee-am-certificate/gravitee-am-certificate-pkcs12/pom.xml b/gravitee-am-certificate/gravitee-am-certificate-pkcs12/pom.xml
index 2b280adfa6..65ea21cb91 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-pkcs12/pom.xml
+++ b/gravitee-am-certificate/gravitee-am-certificate-pkcs12/pom.xml
@@ -22,14 +22,14 @@
     <parent>
         <artifactId>gravitee-am-certificate</artifactId>
         <groupId>io.gravitee.am.certificate</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>gravitee-am-certificate-pkcs12</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Certificate - PKCS12</name>
 
     <dependencies>
diff --git a/gravitee-am-certificate/pom.xml b/gravitee-am-certificate/pom.xml
index 52a5919660..39524aecbe 100644
--- a/gravitee-am-certificate/pom.xml
+++ b/gravitee-am-certificate/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-parent</artifactId>
         <groupId>io.gravitee.am</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-common/pom.xml b/gravitee-am-common/pom.xml
index 1ecaa06423..b660ed72e8 100644
--- a/gravitee-am-common/pom.xml
+++ b/gravitee-am-common/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-parent</artifactId>
         <groupId>io.gravitee.am</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.common</groupId>
diff --git a/gravitee-am-extensiongrant/gravitee-am-extensiongrant-api/pom.xml b/gravitee-am-extensiongrant/gravitee-am-extensiongrant-api/pom.xml
index ad4590c1b5..d5ef7c501b 100644
--- a/gravitee-am-extensiongrant/gravitee-am-extensiongrant-api/pom.xml
+++ b/gravitee-am-extensiongrant/gravitee-am-extensiongrant-api/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-extensiongrant</artifactId>
         <groupId>io.gravitee.am.extensiongrant</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-extensiongrant/gravitee-am-extensiongrant-jwtbearer/pom.xml b/gravitee-am-extensiongrant/gravitee-am-extensiongrant-jwtbearer/pom.xml
index cb70fd93f0..5831d4386f 100644
--- a/gravitee-am-extensiongrant/gravitee-am-extensiongrant-jwtbearer/pom.xml
+++ b/gravitee-am-extensiongrant/gravitee-am-extensiongrant-jwtbearer/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-extensiongrant</artifactId>
         <groupId>io.gravitee.am.extensiongrant</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-extensiongrant/pom.xml b/gravitee-am-extensiongrant/pom.xml
index 9d53e51389..1c44c597bd 100644
--- a/gravitee-am-extensiongrant/pom.xml
+++ b/gravitee-am-extensiongrant/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -30,7 +30,7 @@
     <artifactId>gravitee-am-extensiongrant</artifactId>
     <packaging>pom</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Extension Grant</name>
 
     <modules>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-core/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-core/pom.xml
index 995775830a..f6e123ed47 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-core/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-core/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-gateway</artifactId>
         <groupId>io.gravitee.am.gateway</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-api/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-api/pom.xml
index 4515e86233..3cffec1a65 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-api/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway.handler</groupId>
         <artifactId>gravitee-am-gateway-handler</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-handler-api</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/pom.xml
index 8552d0e908..a9361ce477 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-gateway-handler</artifactId>
         <groupId>io.gravitee.am.gateway.handler</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-handler-common</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/ClientSyncService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/ClientSyncService.java
index cac32c226c..2aba304dac 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/ClientSyncService.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/ClientSyncService.java
@@ -18,6 +18,9 @@ package io.gravitee.am.gateway.handler.common.client;
 import io.gravitee.am.model.Client;
 import io.gravitee.common.service.Service;
 import io.reactivex.Maybe;
+import io.reactivex.Single;
+
+import java.util.List;
 
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
@@ -31,6 +34,8 @@ public interface ClientSyncService extends Service {
 
     Maybe<Client> findByDomainAndClientId(String domain, String clientId);
 
+    Single<List<Client>> findTemplates();
+
     Client addDynamicClientRegistred(Client client);
 
     Client removeDynamicClientRegistred(Client client);
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/impl/ClientSyncServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/impl/ClientSyncServiceImpl.java
index 537868e2a5..578a034bf6 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/impl/ClientSyncServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/client/impl/ClientSyncServiceImpl.java
@@ -27,13 +27,16 @@ import io.gravitee.common.event.EventListener;
 import io.gravitee.common.service.AbstractService;
 import io.reactivex.Maybe;
 import io.reactivex.Observable;
+import io.reactivex.Single;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -48,6 +51,8 @@ public class ClientSyncServiceImpl extends AbstractService implements ClientSync
 
     private final Logger logger = LoggerFactory.getLogger(ClientSyncServiceImpl.class);
     private ConcurrentMap<String, Set<Client>> domainsClients = new ConcurrentHashMap<>();
+    private ConcurrentMap<String, Set<Client>> domainsTemplates = new ConcurrentHashMap<>();
+
 
     @Autowired
     private Domain domain;
@@ -77,6 +82,12 @@ public class ClientSyncServiceImpl extends AbstractService implements ClientSync
                 .firstElement();
     }
 
+    @Override
+    public Single<List<Client>> findTemplates() {
+        Set<Client> templates = this.domainsTemplates.get(domain.getId());
+        return Single.just(templates==null?Collections.emptyList():new ArrayList<>(templates));
+    }
+
     @Override
     public Client addDynamicClientRegistred(Client client) {
         this.updateClients(Collections.singleton(client));
@@ -145,22 +156,57 @@ public class ClientSyncServiceImpl extends AbstractService implements ClientSync
 
     private void removeClient(String idClient, String domainId) {
         logger.info("Domain {} has received client event, delete client {}", domain.getName(), idClient);
-        domainsClients.get(domainId).removeIf(client -> client.getId().equals(idClient));
+        if(domainsClients.get(domainId) != null) {
+            domainsClients.get(domainId).removeIf(client -> client!=null && client.getId().equals(idClient));
+        }
+        if(domainsTemplates.get(domainId)!=null) {
+            domainsTemplates.get(domainId).removeIf(template -> template!=null && template.getId().equals(idClient));
+        }
     }
 
     private void updateClients(Set<Client> clients) {
         clients.forEach(client -> {
-            Set<Client> existingDomainClients = domainsClients.get(client.getDomain());
-            if (existingDomainClients != null) {
-                Set<Client> updateClients = new HashSet<>(existingDomainClients);
-                if (updateClients.contains(client)) {
-                    updateClients.remove(client);
-                }
+            this.updateClients(client);
+            this.updateTemplates(client);
+        });
+    }
+
+    private void updateClients(Client client) {
+        Set<Client> existingDomainClients = domainsClients.get(client.getDomain());
+
+        if (existingDomainClients != null) {
+            Set<Client> updateClients = new HashSet<>(existingDomainClients);
+
+            updateClients.remove(client);
+
+            if(!client.isTemplate()) {
                 updateClients.add(client);
-                domainsClients.put(client.getDomain(), updateClients);
-            } else {
-                domainsClients.put(client.getDomain(), Collections.singleton(client));
             }
-        });
+            domainsClients.put(client.getDomain(), updateClients);
+        } else {
+            if(!client.isTemplate()) {
+                //Collections.singleton does not support removeIf
+                domainsClients.put(client.getDomain(), new HashSet(Collections.singleton(client)));
+            }
+        }
+    }
+
+    private void updateTemplates(Client template) {
+        Set<Client> existingDomainTemplates = domainsTemplates.get(template.getDomain());
+        if (existingDomainTemplates != null) {
+            Set<Client> updateTemplates = new HashSet<>(existingDomainTemplates);
+
+            updateTemplates.remove(template);
+
+            if(template.isTemplate()) {
+                updateTemplates.add(template);
+            }
+            domainsTemplates.put(template.getDomain(), updateTemplates);
+        } else {
+            if(template.isTemplate()) {
+                //Collections.singleton does not support removeIf
+                domainsTemplates.put(template.getDomain(), new HashSet(Collections.singleton(template)));
+            }
+        }
     }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-context/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-context/pom.xml
index a7ec2813ea..4f29a4d1a1 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-context/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-context/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-gateway-handler</artifactId>
         <groupId>io.gravitee.am.gateway.handler</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/pom.xml
index 71036210e6..706ddb0275 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway.handler</groupId>
         <artifactId>gravitee-am-gateway-handler</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-handler-core</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/pom.xml
index f9a002f398..34d4e91fd6 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-gateway-handler</artifactId>
         <groupId>io.gravitee.am.gateway.handler</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-handler-oidc</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/OIDCProvider.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/OIDCProvider.java
index cf51ffde8a..f86a786985 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/OIDCProvider.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/OIDCProvider.java
@@ -31,10 +31,10 @@ import io.gravitee.am.gateway.handler.oidc.resources.endpoint.*;
 import io.gravitee.am.gateway.handler.oidc.resources.handler.DynamicClientAccessHandler;
 import io.gravitee.am.gateway.handler.oidc.resources.handler.DynamicClientAccessTokenHandler;
 import io.gravitee.am.gateway.handler.oidc.resources.handler.DynamicClientRegistrationHandler;
+import io.gravitee.am.gateway.handler.oidc.resources.handler.DynamicClientRegistrationTemplateHandler;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationService;
 import io.gravitee.am.gateway.handler.oidc.service.discovery.OpenIDDiscoveryService;
 import io.gravitee.am.model.Domain;
-import io.gravitee.am.service.ClientService;
 import io.gravitee.am.service.UserService;
 import io.gravitee.common.http.MediaType;
 import io.gravitee.common.service.AbstractService;
@@ -51,6 +51,7 @@ import static io.gravitee.am.common.oauth2.Parameters.CLIENT_ID;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 public class OIDCProvider extends AbstractService<ProtocolProvider> implements ProtocolProvider {
@@ -73,9 +74,6 @@ public class OIDCProvider extends AbstractService<ProtocolProvider> implements P
     @Autowired
     private ClientSyncService clientSyncService;
 
-    @Autowired
-    private ClientService clientService;
-
     @Autowired
     private JWTService jwtService;
 
@@ -170,6 +168,14 @@ public class OIDCProvider extends AbstractService<ProtocolProvider> implements P
                 .route(HttpMethod.GET, "/.well-known/jwks.json")
                 .handler(openIDProviderJWKSetEndpoint);
 
+        // Dynamic Client Registration templates
+        DynamicClientRegistrationTemplateHandler dynamicClientRegistrationTemplateHandler = new DynamicClientRegistrationTemplateHandler(domain);
+        DynamicClientRegistrationTemplateEndpoint dynamicClientRegistrationTemplateEndpoint = new DynamicClientRegistrationTemplateEndpoint(clientSyncService);
+        oidcRouter
+                .route(HttpMethod.GET, "/register_templates")
+                .handler(dynamicClientRegistrationTemplateHandler)
+                .handler(dynamicClientRegistrationTemplateEndpoint);
+
         // Dynamic Client Registration
         OAuth2AuthHandler dynamicClientRegistrationAuthHandler = OAuth2AuthHandler.create(oAuth2AuthProvider, Scope.DCR_ADMIN.getKey());
         dynamicClientRegistrationAuthHandler.extractToken(true);
@@ -177,7 +183,7 @@ public class OIDCProvider extends AbstractService<ProtocolProvider> implements P
         dynamicClientRegistrationAuthHandler.forceClientToken(true);
 
         DynamicClientRegistrationHandler dynamicClientRegistrationHandler = new DynamicClientRegistrationHandler(domain, dynamicClientRegistrationAuthHandler);
-        DynamicClientRegistrationEndpoint dynamicClientRegistrationEndpoint = new DynamicClientRegistrationEndpoint(dcrService, clientService, clientSyncService);
+        DynamicClientRegistrationEndpoint dynamicClientRegistrationEndpoint = new DynamicClientRegistrationEndpoint(dcrService, clientSyncService);
         oidcRouter
                 .route(HttpMethod.POST, "/register")
                 .consumes(MediaType.APPLICATION_JSON)
@@ -195,7 +201,7 @@ public class OIDCProvider extends AbstractService<ProtocolProvider> implements P
 
         DynamicClientAccessHandler dynamicClientAccessHandler = new DynamicClientAccessHandler(domain);
         DynamicClientAccessTokenHandler dynamicClientAccessTokenHandler = new DynamicClientAccessTokenHandler();
-        DynamicClientAccessEndpoint dynamicClientAccessEndpoint = new DynamicClientAccessEndpoint(dcrService, clientService, clientSyncService);
+        DynamicClientAccessEndpoint dynamicClientAccessEndpoint = new DynamicClientAccessEndpoint(dcrService, clientSyncService);
         oidcRouter
                 .route(HttpMethod.GET, "/register/:"+CLIENT_ID)
                 .handler(dynamicClientAccessHandler)
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpoint.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpoint.java
index be5ada7580..7626796f90 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpoint.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpoint.java
@@ -21,7 +21,6 @@ import io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationResponse;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationService;
 import io.gravitee.am.model.Client;
-import io.gravitee.am.service.ClientService;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.HttpStatusCode;
 import io.gravitee.common.http.MediaType;
@@ -44,8 +43,8 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
 
     private static final Logger LOGGER = LoggerFactory.getLogger(DynamicClientAccessEndpoint.class);
 
-    public DynamicClientAccessEndpoint(DynamicClientRegistrationService dcrService, ClientService clientService, ClientSyncService clientSyncService) {
-        super(dcrService, clientService, clientSyncService);
+    public DynamicClientAccessEndpoint(DynamicClientRegistrationService dcrService, ClientSyncService clientSyncService) {
+        super(dcrService, clientSyncService);
     }
 
     /**
@@ -87,10 +86,7 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
         this.getClient(context)
                 .flatMapSingle(Single::just)
                 .flatMap(client -> this.extractRequest(context)
-                        .flatMap(dcrService::validateClientPatchRequest)
-                        .map(request -> request.patch(client))
-                        .flatMap(updatedClient -> dcrService.applyRegistrationAccessToken(UriBuilderRequest.extractBasePath(context), updatedClient))
-                        .flatMap(clientService::update)
+                        .flatMap(request -> dcrService.patch(client, request, UriBuilderRequest.extractBasePath(context)))
                         .map(clientSyncService::addDynamicClientRegistred)
                 )
                 .subscribe(
@@ -114,10 +110,7 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
         this.getClient(context)
                 .flatMapSingle(Single::just)
                 .flatMap(client -> this.extractRequest(context)
-                        .flatMap(dcrService::validateClientRegistrationRequest)
-                        .map(request -> request.update(client))
-                        .flatMap(updatedClient -> dcrService.applyRegistrationAccessToken(UriBuilderRequest.extractBasePath(context), updatedClient))
-                        .flatMap(clientService::update)
+                        .flatMap(request -> dcrService.update(client, request, UriBuilderRequest.extractBasePath(context)))
                         .map(clientSyncService::addDynamicClientRegistred)
                 )
                 .subscribe(
@@ -139,7 +132,7 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
         LOGGER.debug("Dynamic client registration DELETE endpoint");
 
         this.getClient(context)
-                .flatMapSingle(client -> this.clientService.delete(client.getId()).toSingleDefault(client))
+                .flatMapSingle(dcrService::delete)
                 .map(this.clientSyncService::removeDynamicClientRegistred)
                 .subscribe(
                         client -> context.response().setStatusCode(HttpStatusCode.NO_CONTENT_204).end()
@@ -156,8 +149,7 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
 
         this.getClient(context)
                 .flatMapSingle(Single::just)
-                .flatMap(client -> dcrService.applyRegistrationAccessToken(UriBuilderRequest.extractBasePath(context), client))
-                .flatMap(clientService::renewClientSecret)
+                .flatMap(toRenew -> dcrService.renewSecret(toRenew, UriBuilderRequest.extractBasePath(context)))
                 .map(clientSyncService::addDynamicClientRegistred)
                 .subscribe(
                         client -> context.response()
@@ -174,7 +166,6 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
         String clientId = context.request().getParam("client_id");
 
         return this.clientSyncService.findByClientId(clientId)
-                .map(Client::clone)
                 .switchIfEmpty(Maybe.error(new OAuth2Exception() {
                     @Override
                     public int getHttpStatusCode() {
@@ -185,6 +176,7 @@ public class DynamicClientAccessEndpoint extends DynamicClientRegistrationEndpoi
                     public String getMessage() {
                         return "client not found";
                     }
-                }));
+                }))
+                .map(Client::clone);
     }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpoint.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpoint.java
index 75272631c3..8ab826a422 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpoint.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpoint.java
@@ -18,10 +18,9 @@ package io.gravitee.am.gateway.handler.oidc.resources.endpoint;
 import io.gravitee.am.gateway.handler.common.client.ClientSyncService;
 import io.gravitee.am.gateway.handler.common.jwk.converter.JWKSetDeserializer;
 import io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest;
-import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationService;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationRequest;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationResponse;
-import io.gravitee.am.service.ClientService;
+import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationService;
 import io.gravitee.am.service.exception.InvalidClientMetadataException;
 import io.gravitee.common.http.HttpHeaders;
 import io.gravitee.common.http.HttpStatusCode;
@@ -34,8 +33,6 @@ import io.vertx.reactivex.ext.web.RoutingContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.URISyntaxException;
-
 /**
  * Dynamic Client Registration is a protocol that allows OAuth client applications to register with an OAuth server.
  * Specifications are defined by OpenID Foundation and by the IETF as RFC 7591 too.
@@ -49,15 +46,13 @@ import java.net.URISyntaxException;
  */
 public class DynamicClientRegistrationEndpoint implements Handler<RoutingContext> {
 
-    protected ClientService clientService;
     protected ClientSyncService clientSyncService;
     protected DynamicClientRegistrationService dcrService;
 
     private static final Logger LOGGER = LoggerFactory.getLogger(DynamicClientRegistrationEndpoint.class);
 
-    public DynamicClientRegistrationEndpoint(DynamicClientRegistrationService dcrService, ClientService clientService, ClientSyncService clientSyncService) {
+    public DynamicClientRegistrationEndpoint(DynamicClientRegistrationService dcrService, ClientSyncService clientSyncService) {
         this.dcrService = dcrService;
-        this.clientService = clientService;
         this.clientSyncService = clientSyncService;
     }
 
@@ -70,12 +65,7 @@ public class DynamicClientRegistrationEndpoint implements Handler<RoutingContext
         LOGGER.debug("Dynamic client registration CREATE endpoint");
 
         this.extractRequest(context)
-                .flatMap(dcrService::validateClientRegistrationRequest)
-                .map(request -> dcrService.create(request))
-                .flatMap(dcrService::applyDefaultIdentityProvider)
-                .flatMap(dcrService::applyDefaultCertificateProvider)
-                .flatMap(client -> dcrService.applyRegistrationAccessToken(UriBuilderRequest.extractBasePath(context), client))
-                .flatMap(clientService::create)
+                .flatMap(request -> dcrService.create(request, UriBuilderRequest.extractBasePath(context)))
                 .map(clientSyncService::addDynamicClientRegistred)
                 .subscribe(
                         client -> context.response()
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationTemplateEndpoint.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationTemplateEndpoint.java
new file mode 100644
index 0000000000..f6fa5e6abd
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationTemplateEndpoint.java
@@ -0,0 +1,58 @@
+/**
+ * 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.oidc.resources.endpoint;
+
+import io.gravitee.am.gateway.handler.common.client.ClientSyncService;
+import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationTemplate;
+import io.gravitee.common.http.HttpHeaders;
+import io.gravitee.common.http.HttpStatusCode;
+import io.gravitee.common.http.MediaType;
+import io.vertx.core.Handler;
+import io.vertx.core.json.Json;
+import io.vertx.reactivex.ext.web.RoutingContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+public class DynamicClientRegistrationTemplateEndpoint implements Handler<RoutingContext> {
+
+    private ClientSyncService clientSyncService;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicClientRegistrationTemplateEndpoint.class);
+
+    public DynamicClientRegistrationTemplateEndpoint(ClientSyncService clientSyncService) {
+        this.clientSyncService = clientSyncService;
+    }
+
+    @Override
+    public void handle(RoutingContext context) {
+        LOGGER.debug("Dynamic client registration TEMPLATE endpoint");
+
+        this.clientSyncService.findTemplates()
+                .subscribe(
+                        templates -> context.response()
+                                .putHeader(HttpHeaders.CACHE_CONTROL, "no-store")
+                                .putHeader(HttpHeaders.PRAGMA, "no-cache")
+                                .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+                                .setStatusCode(HttpStatusCode.OK_200)
+                                .end(Json.encodePrettily(DynamicClientRegistrationTemplate.from(templates)))
+                        , error -> context.fail(error)
+                );
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/handler/DynamicClientRegistrationTemplateHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/handler/DynamicClientRegistrationTemplateHandler.java
new file mode 100644
index 0000000000..b6459f6b65
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/resources/handler/DynamicClientRegistrationTemplateHandler.java
@@ -0,0 +1,44 @@
+/**
+ * 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.oidc.resources.handler;
+
+import io.gravitee.am.gateway.handler.oidc.exception.ClientRegistrationForbiddenException;
+import io.gravitee.am.model.Domain;
+import io.vertx.core.Handler;
+import io.vertx.reactivex.ext.web.RoutingContext;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+public class DynamicClientRegistrationTemplateHandler implements Handler<RoutingContext> {
+    private Domain domain;
+
+    public DynamicClientRegistrationTemplateHandler(Domain domain) {
+        this.domain = domain;
+    }
+
+    @Override
+    public void handle(RoutingContext context) {
+        //Only allow access if dcr & template are enabled
+        if(domain.isDynamicClientRegistrationEnabled() && domain.isDynamicClientRegistrationTemplateEnabled()) {
+            context.next();
+            return;
+        }
+        //Else fail...
+        context.fail(new ClientRegistrationForbiddenException());
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationService.java
index e29c06c7b6..057e6d5435 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationService.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationService.java
@@ -24,15 +24,13 @@ import io.reactivex.Single;
  */
 public interface DynamicClientRegistrationService {
 
-    Client create(DynamicClientRegistrationRequest request);
+    Single<Client> create(DynamicClientRegistrationRequest request, String basePath);
 
-    Single<Client> applyDefaultIdentityProvider(Client client);
+    Single<Client> patch(Client toPatch, DynamicClientRegistrationRequest request, String basePath);
 
-    Single<Client> applyDefaultCertificateProvider(Client client);
+    Single<Client> update(Client toUpdate, DynamicClientRegistrationRequest request, String basePath);
 
-    Single<Client> applyRegistrationAccessToken(String basePath, Client client);
+    Single<Client> delete(Client toDelete);
 
-    Single<DynamicClientRegistrationRequest> validateClientRegistrationRequest(final DynamicClientRegistrationRequest request);
-
-    Single<DynamicClientRegistrationRequest> validateClientPatchRequest(final DynamicClientRegistrationRequest request);
+    Single<Client> renewSecret(Client toRenew, String basePath);
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationTemplate.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationTemplate.java
new file mode 100644
index 0000000000..02522a5e91
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationTemplate.java
@@ -0,0 +1,68 @@
+/**
+ * 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.oidc.service.clientregistration;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.gravitee.am.model.Client;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class DynamicClientRegistrationTemplate {
+
+    @JsonProperty("software_id")
+    private String softwareId;
+
+    @JsonProperty("description")
+    private String description;
+
+    public String getSoftwareId() {
+        return softwareId;
+    }
+
+    public void setSoftwareId(String softwareId) {
+        this.softwareId = softwareId;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public static List<DynamicClientRegistrationTemplate> from(List<Client> templates) {
+        if(templates==null) {
+            return Collections.emptyList();
+        }
+        return templates.stream()
+                .map(template -> {
+                    DynamicClientRegistrationTemplate res = new DynamicClientRegistrationTemplate();
+                    res.setSoftwareId(template.getId());
+                    res.setDescription(template.getClientName());
+                    return res;
+                })
+                .collect(Collectors.toList());
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/impl/DynamicClientRegistrationServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/impl/DynamicClientRegistrationServiceImpl.java
index 197fe6d630..5bac01259e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/impl/DynamicClientRegistrationServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/impl/DynamicClientRegistrationServiceImpl.java
@@ -29,6 +29,9 @@ import io.gravitee.am.gateway.handler.oidc.service.utils.SubjectTypeUtils;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.Domain;
 import io.gravitee.am.service.CertificateService;
+import io.gravitee.am.service.ClientService;
+import io.gravitee.am.service.EmailTemplateService;
+import io.gravitee.am.service.FormService;
 import io.gravitee.am.service.IdentityProviderService;
 import io.gravitee.am.service.exception.InvalidClientMetadataException;
 import io.gravitee.am.service.exception.InvalidRedirectUriException;
@@ -49,7 +52,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
@@ -75,6 +77,9 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
     @Autowired
     private CertificateService certificateService;
 
+    @Autowired
+    private ClientService clientService;
+
     @Autowired
     private JWKService jwkService;
 
@@ -87,12 +92,110 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
     @Autowired
     private Domain domain;
 
+    @Autowired
+    private FormService formService;
+
+    @Autowired
+    private EmailTemplateService emailTemplateService;
+
+    @Override
+    public Single<Client> create(DynamicClientRegistrationRequest request, String basePath) {
+        //If Dynamic client registration from template is enabled and request contains a software_id
+        if(domain.isDynamicClientRegistrationTemplateEnabled() && request.getSoftwareId()!=null && request.getSoftwareId().isPresent()) {
+            return this.createClientFromTemplate(request, basePath);
+        }
+        return this.createClientFromRequest(request,basePath);
+    }
+
     @Override
-    public Client create(DynamicClientRegistrationRequest request) {
+    public Single<Client> patch(Client toPatch, DynamicClientRegistrationRequest request, String basePath) {
+        return this.validateClientPatchRequest(request)
+                .map(req -> req.patch(toPatch))
+                .flatMap(app -> this.applyRegistrationAccessToken(basePath, app))
+                .flatMap(clientService::update);
+    }
+
+    @Override
+    public Single<Client> update(Client toUpdate, DynamicClientRegistrationRequest request, String basePath) {
+        return this.validateClientRegistrationRequest(request)
+                .map(req -> req.patch(toUpdate))
+                .flatMap(app -> this.applyRegistrationAccessToken(basePath, app))
+                .flatMap(clientService::update);
+    }
+
+    @Override
+    public Single<Client> delete(Client toDelete) {
+        return this.clientService.delete(toDelete.getId()).toSingleDefault(toDelete);
+    }
+
+    @Override
+    public Single<Client> renewSecret(Client toRenew, String basePath) {
+        return this.applyRegistrationAccessToken(basePath, toRenew)
+                .flatMap(clientService::renewClientSecret);
+    }
+
+    private Single<Client> createClientFromRequest(DynamicClientRegistrationRequest request, String basePath) {
         Client client = new Client();
         client.setClientId(SecureRandomString.generate());
         client.setDomain(domain.getId());
-        return request.patch(client);
+
+        return this.validateClientRegistrationRequest(request)
+                .map(req -> req.patch(client))
+                .flatMap(this::applyDefaultIdentityProvider)
+                .flatMap(this::applyDefaultCertificateProvider)
+                .flatMap(app -> this.applyRegistrationAccessToken(basePath, app))
+                .flatMap(clientService::create);
+    }
+
+    /**
+     * <pre>
+     * Software_id is based on id field and not client_id because:
+     * this field is not intended to be human readable and is usually opaque to the client and authorization server.
+     * the client may switch back from template to real client and then this is better to not expose it's client_id.
+     * @param request
+     * @param basePath
+     * @return
+     * </pre>
+     */
+    private Single<Client> createClientFromTemplate(DynamicClientRegistrationRequest request, String basePath) {
+        return clientService.findById(request.getSoftwareId().get())
+                .switchIfEmpty(Maybe.error(new InvalidClientMetadataException("No template found for software_id "+request.getSoftwareId().get())))
+                .flatMapSingle(this::sanitizeTemplate)
+                .map(request::patch)
+                .flatMap(app -> this.applyRegistrationAccessToken(basePath, app))
+                .flatMap(clientService::create)
+                .flatMap(client -> copyForms(request.getSoftwareId().get(),client))
+                .flatMap(client -> copyEmails(request.getSoftwareId().get(),client));
+    }
+
+    private Single<Client> sanitizeTemplate(Client template) {
+        if(!template.isTemplate()) {
+            return Single.error(new InvalidClientMetadataException("Client behind software_id is not a template"));
+        }
+        //Erase potential confidential values.
+        template.setClientId(SecureRandomString.generate());
+        template.setDomain(domain.getId());
+        template.setId(null);
+        template.setClientSecret(null);
+        template.setClientName(null);
+        template.setRedirectUris(null);
+        template.setSectorIdentifierUri(null);
+        template.setJwks(null);
+        template.setJwksUri(null);
+        //Set it as non template
+        template.setTemplate(false);
+
+        return Single.just(template);
+    }
+
+    private Single<Client> copyForms(String sourceId, Client client) {
+        return formService.copyFromClient(domain.getId(), sourceId, client.getId())
+                .flatMap(irrelevant -> Single.just(client));
+    }
+
+    private Single<Client> copyEmails(String sourceId, Client client) {
+        return emailTemplateService.copyFromClient(domain.getId(), sourceId, client.getId())
+                .flatMap(irrelevant -> Single.just(client));
     }
 
     /**
@@ -101,8 +204,7 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
      * @param client App to create
      * @return
      */
-    @Override
-    public Single<Client> applyDefaultIdentityProvider(Client client) {
+    private Single<Client> applyDefaultIdentityProvider(Client client) {
         return identityProviderService.findByDomain(client.getDomain())
             .map(identityProviders -> {
                 if(identityProviders!=null && !identityProviders.isEmpty()) {
@@ -118,8 +220,7 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
      * @param client App to create
      * @return
      */
-    @Override
-    public Single<Client> applyDefaultCertificateProvider(Client client) {
+    private Single<Client> applyDefaultCertificateProvider(Client client) {
         return certificateService.findByDomain(client.getDomain())
                 .map(certificates -> {
                     if(certificates!=null && !certificates.isEmpty()) {
@@ -129,8 +230,7 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
                 });
     }
 
-    @Override
-    public Single<Client> applyRegistrationAccessToken(String basePath, Client client) {
+    private Single<Client> applyRegistrationAccessToken(String basePath, Client client) {
 
         OpenIDProviderMetadata openIDProviderMetadata = openIDDiscoveryService.getConfiguration(basePath);
 
@@ -159,15 +259,14 @@ public class DynamicClientRegistrationServiceImpl implements DynamicClientRegist
      *
      * @param request DynamicClientRegistrationRequest
      */
-    @Override
-    public Single<DynamicClientRegistrationRequest> validateClientRegistrationRequest(final DynamicClientRegistrationRequest request) {
+    private Single<DynamicClientRegistrationRequest> validateClientRegistrationRequest(final DynamicClientRegistrationRequest request) {
         LOGGER.debug("Validating dynamic client registration payload");
         return this.validateClientRegistrationRequest(request,false);
     }
 
-    @Override
-    public Single<DynamicClientRegistrationRequest> validateClientPatchRequest(DynamicClientRegistrationRequest request) {
+    private Single<DynamicClientRegistrationRequest> validateClientPatchRequest(DynamicClientRegistrationRequest request) {
         LOGGER.debug("Validating dynamic client registration payload : patch");
+        //redirect_uri is mandatory in the request, but in case of patch we may ommit it...
         return this.validateClientRegistrationRequest(request,true);
     }
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/OpenIDProviderMetadata.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/OpenIDProviderMetadata.java
index 9036df4d3c..d0f7c2d312 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/OpenIDProviderMetadata.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/OpenIDProviderMetadata.java
@@ -60,6 +60,9 @@ public class OpenIDProviderMetadata {
     @JsonProperty("registration_renew_secret_endpoint")
     private String registrationRenewSecretEndpoint;
 
+    @JsonProperty("registration_templates_endpoint")
+    private String registrationTemplatesEndpoint;
+
     @JsonProperty("scopes_supported")
     private List<String> scopesSupported;
 
@@ -230,6 +233,14 @@ public class OpenIDProviderMetadata {
         this.registrationRenewSecretEndpoint = registrationRenewSecretEndpoint;
     }
 
+    public String getRegistrationTemplatesEndpoint() {
+        return registrationTemplatesEndpoint;
+    }
+
+    public void setRegistrationTemplatesEndpoint(String registrationTemplatesEndpoint) {
+        this.registrationTemplatesEndpoint = registrationTemplatesEndpoint;
+    }
+
     public List<String> getScopesSupported() {
         return scopesSupported;
     }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java
index 61ea1aa537..18abc5b772 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java
@@ -74,6 +74,9 @@ public class OpenIDDiscoveryServiceImpl implements OpenIDDiscoveryService {
         openIDProviderMetadata.setEndSessionEndpoint(getEndpointAbsoluteURL(basePath, ENDSESSION_ENDPOINT));
         openIDProviderMetadata.setRegistrationEndpoint(getEndpointAbsoluteURL(basePath, REGISTRATION_ENDPOINT));
         openIDProviderMetadata.setRegistrationRenewSecretEndpoint(openIDProviderMetadata.getRegistrationEndpoint()+"/:client_id/renew_secret");
+        if(domain.isDynamicClientRegistrationTemplateEnabled()) {
+            openIDProviderMetadata.setRegistrationTemplatesEndpoint(openIDProviderMetadata.getRegistrationEndpoint()+"_templates");
+        }
 
         // supported parameters
         openIDProviderMetadata.setScopesSupported(scopeService.getDiscoveryScope());
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/pom.xml
index 5e8f8b3307..482934f17d 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway.handler</groupId>
         <artifactId>gravitee-am-gateway-handler</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-users/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-users/pom.xml
index 7ab980a3c8..9cc5c0668a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-users/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-users/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-gateway-handler</artifactId>
         <groupId>io.gravitee.am.gateway.handler</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-handler-users</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-handler/pom.xml
index 07731bbf1c..3bcb18a606 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway</groupId>
         <artifactId>gravitee-am-gateway</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.gateway.handler</groupId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-policy/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-policy/pom.xml
index 8fa79e3641..c3842f5a72 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-policy/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-policy/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-gateway</artifactId>
         <groupId>io.gravitee.am.gateway</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-reactor/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-reactor/pom.xml
index b780e9c814..317cf3aee9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-reactor/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-reactor/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway</groupId>
         <artifactId>gravitee-am-gateway</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-reactor</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-repository/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-repository/pom.xml
index 65320e1569..9c9b85155a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-repository/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-repository/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-gateway</artifactId>
         <groupId>io.gravitee.am.gateway</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-repository</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/pom.xml
index e6b1e09f16..8f1b62500e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-gateway-services</artifactId>
         <groupId>io.gravitee.am.gateway</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-services/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-services/pom.xml
index dabf567257..830ec3d87f 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-services/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-services/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-gateway</artifactId>
         <groupId>io.gravitee.am.gateway</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-services</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-bootstrap/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-bootstrap/pom.xml
index eb9f92ad8c..a45f221c30 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-bootstrap/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-bootstrap/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway.standalone</groupId>
         <artifactId>gravitee-am-gateway-standalone</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-standalone-bootstrap</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/pom.xml
index 6edee51775..d265e7c294 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway.standalone</groupId>
         <artifactId>gravitee-am-gateway-standalone</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-standalone-container</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/gravitee-am-gateway-standalone-distribution-zip/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/gravitee-am-gateway-standalone-distribution-zip/pom.xml
index a93f08da32..3315137ab7 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/gravitee-am-gateway-standalone-distribution-zip/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/gravitee-am-gateway-standalone-distribution-zip/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway.standalone</groupId>
         <artifactId>gravitee-am-gateway-standalone-distribution</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.gateway.standalone</groupId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
index fa90395a56..d67f0bed3b 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-gateway-standalone</artifactId>
         <groupId>io.gravitee.am.gateway.standalone</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-gateway-standalone-distribution</artifactId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/pom.xml
index 4596b42a90..a797312646 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>io.gravitee.am.gateway</groupId>
         <artifactId>gravitee-am-gateway</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.gateway.standalone</groupId>
diff --git a/gravitee-am-gateway/pom.xml b/gravitee-am-gateway/pom.xml
index c92eef14c0..72e540cc3c 100644
--- a/gravitee-am-gateway/pom.xml
+++ b/gravitee-am-gateway/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.gateway</groupId>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-api/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-api/pom.xml
index 851d0599cc..c5694cbe54 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-api/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-api/pom.xml
@@ -23,14 +23,14 @@
     <parent>
         <groupId>io.gravitee.am.identityprovider</groupId>
         <artifactId>gravitee-am-identityprovider</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.identityprovider</groupId>
     <artifactId>gravitee-am-identityprovider-api</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Identity Provider - API</name>
 
     <dependencies>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-github/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-github/pom.xml
index d813aab5f9..7c8cfd393d 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-github/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-github/pom.xml
@@ -22,13 +22,13 @@
     <parent>
         <artifactId>gravitee-am-identityprovider</artifactId>
         <groupId>io.gravitee.am.identityprovider</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>gravitee-am-identityprovider-github</artifactId>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Identity Provider - GitHub</name>
 
     <dependencies>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/pom.xml
index 14a88bab76..7c0e1858db 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/pom.xml
@@ -23,13 +23,13 @@
     <parent>
         <groupId>io.gravitee.am.identityprovider</groupId>
         <artifactId>gravitee-am-identityprovider</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-identityprovider-inline</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Identity Provider - Inline</name>
 
     <dependencies>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-ldap/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-ldap/pom.xml
index 628db55ca4..648925882b 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-ldap/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-ldap/pom.xml
@@ -23,13 +23,13 @@
     <parent>
         <groupId>io.gravitee.am.identityprovider</groupId>
         <artifactId>gravitee-am-identityprovider</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-identityprovider-ldap</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Identity Provider - LDAP</name>
 
     <properties>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/pom.xml
index 57250751c2..93658bc1a2 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/pom.xml
@@ -23,13 +23,13 @@
     <parent>
         <groupId>io.gravitee.am.identityprovider</groupId>
         <artifactId>gravitee-am-identityprovider</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-identityprovider-mongo</artifactId>
     <packaging>jar</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Identity Provider - MongoDB</name>
 
     <properties>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/pom.xml
index 0e03b85521..e2f14e272c 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-oauth2-generic/pom.xml
@@ -22,13 +22,13 @@
     <parent>
         <artifactId>gravitee-am-identityprovider</artifactId>
         <groupId>io.gravitee.am.identityprovider</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>gravitee-am-identityprovider-oauth2-generic</artifactId>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Identity Provider - OpenID Connect</name>
 
     <properties>
diff --git a/gravitee-am-identityprovider/pom.xml b/gravitee-am-identityprovider/pom.xml
index 81dab0b8e5..be6609891a 100644
--- a/gravitee-am-identityprovider/pom.xml
+++ b/gravitee-am-identityprovider/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.identityprovider</groupId>
diff --git a/gravitee-am-management-api/gravitee-am-management-api-core/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-core/pom.xml
index 5296439ab9..11e07f0de6 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-core/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-core/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/pom.xml
index 964c0f475b..814a81a035 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-admin/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-management-api-handlers</artifactId>
         <groupId>io.gravitee.am.management</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/pom.xml
index 3fe00756e0..30bc15656c 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api-handlers</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-spring/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-spring/pom.xml
index 0520bd7250..c22b1b7de4 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-spring/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-spring/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api-handlers</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-handlers/pom.xml
index 81dd0656b6..d300a1955d 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-jetty/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-jetty/pom.xml
index 68a97b187f..7f0aefea58 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-jetty/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-jetty/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-repository/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-repository/pom.xml
index aa187919f1..96a17152fc 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-repository/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-repository/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-management-api</artifactId>
         <groupId>io.gravitee.am.management</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-service/pom.xml
index 446746f28f..f4df69fbf2 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-services/gravitee-am-management-api-services-sync/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-services/gravitee-am-management-api-services-sync/pom.xml
index 4c1a050390..7a7705647a 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-services/gravitee-am-management-api-services-sync/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-services/gravitee-am-management-api-services-sync/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-management-api-services</artifactId>
         <groupId>io.gravitee.am.management</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-management-api-services-sync</artifactId>
diff --git a/gravitee-am-management-api/gravitee-am-management-api-services/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-services/pom.xml
index bdda95b985..9c0c3a63a5 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-services/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-services/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <artifactId>gravitee-am-management-api</artifactId>
         <groupId>io.gravitee.am.management</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-management-api-services</artifactId>
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-bootstrap/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-bootstrap/pom.xml
index 600a7c8dc5..60c3fba6e4 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-bootstrap/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-bootstrap/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management.standalone</groupId>
         <artifactId>gravitee-am-management-api-standalone</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml
index 99e41a68c5..218c6a6661 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management.standalone</groupId>
         <artifactId>gravitee-am-management-api-standalone</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/gravitee-am-management-api-standalone-distribution-zip/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/gravitee-am-management-api-standalone-distribution-zip/pom.xml
index aca139b02d..322a317eb2 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/gravitee-am-management-api-standalone-distribution-zip/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/gravitee-am-management-api-standalone-distribution-zip/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management.standalone</groupId>
         <artifactId>gravitee-am-management-api-standalone-distribution</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
index 87bb8fe1f2..af19a203ce 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management.standalone</groupId>
         <artifactId>gravitee-am-management-api-standalone</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/pom.xml
index 59648cf5b0..06fcd2c61e 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am.management</groupId>
         <artifactId>gravitee-am-management-api</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-management-api/pom.xml b/gravitee-am-management-api/pom.xml
index 93ed88181c..f8e6f17192 100644
--- a/gravitee-am-management-api/pom.xml
+++ b/gravitee-am-management-api/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-model/pom.xml b/gravitee-am-model/pom.xml
index 94b945e2a6..1e8a8c4e80 100644
--- a/gravitee-am-model/pom.xml
+++ b/gravitee-am-model/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.model</groupId>
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Client.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Client.java
index c23b30de50..b85a9b3ffb 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/Client.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Client.java
@@ -169,6 +169,8 @@ public class Client implements Cloneable{
 
     private List<TokenClaim> tokenCustomClaims;
 
+    private boolean template;
+
     public Client() { }
 
     public Client(Client other) {
@@ -229,6 +231,7 @@ public class Client implements Cloneable{
         this.scopeApprovals = other.scopeApprovals;
         this.accountSettings = other.accountSettings;
         this.tokenCustomClaims = other.tokenCustomClaims;
+        this.template = other.template;
     }
 
     public String getId() {
@@ -690,6 +693,14 @@ public class Client implements Cloneable{
         this.tokenCustomClaims = tokenCustomClaims;
     }
 
+    public boolean isTemplate() {
+        return template;
+    }
+
+    public void setTemplate(boolean template) {
+        this.template = template;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java
index e100314886..728e7ef522 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java
@@ -261,6 +261,12 @@ public class Domain {
                 this.getOidc().getClientRegistrationSettings().isOpenDynamicClientRegistrationEnabled();
     }
 
+    public boolean isDynamicClientRegistrationTemplateEnabled() {
+        return this.getOidc()!=null &&
+                this.getOidc().getClientRegistrationSettings()!=null &&
+                this.getOidc().getClientRegistrationSettings().isClientTemplateEnabled();
+    }
+
     public boolean isRedirectUriLocalhostAllowed() {
         return this.getOidc()!=null &&
                 this.getOidc().getClientRegistrationSettings()!=null &&
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Template.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Template.java
index 2b4c2d863a..10f51a7991 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/Template.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Template.java
@@ -15,6 +15,10 @@
  */
 package io.gravitee.am.model;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
@@ -38,4 +42,19 @@ public enum Template {
     public String template() {
         return template;
     }
+
+    public static Template parse(String toParse) {
+        if(toParse==null || toParse.trim().isEmpty()) {
+            throw new IllegalArgumentException("template must not be null");
+        }
+        List<Template> matchingTemplate = Arrays.stream(Template.values())
+                .filter(template -> template.template().equals(toParse))
+                .collect(Collectors.toList());
+
+        if(matchingTemplate.size()==1) {
+            return matchingTemplate.get(0);
+        }
+
+        throw new IllegalArgumentException("No template is matching");
+    }
 }
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/oidc/ClientRegistrationSettings.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/oidc/ClientRegistrationSettings.java
index 0ddb3b50d6..c5261ffbca 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/oidc/ClientRegistrationSettings.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/oidc/ClientRegistrationSettings.java
@@ -63,6 +63,10 @@ public class ClientRegistrationSettings {
      */
     private List<String> allowedScopes;
 
+    /**
+     * Enable client to be used as template for dynamic client registration
+     */
+    private boolean isClientTemplateEnabled;
 
     public boolean isAllowLocalhostRedirectUri() {
         return allowLocalhostRedirectUri;
@@ -132,4 +136,12 @@ public class ClientRegistrationSettings {
     public void setAllowedScopes(List<String> allowedScopes) {
         this.allowedScopes = allowedScopes;
     }
+
+    public boolean isClientTemplateEnabled() {
+        return isClientTemplateEnabled;
+    }
+
+    public void setClientTemplateEnabled(boolean clientTemplateEnabled) {
+        isClientTemplateEnabled = clientTemplateEnabled;
+    }
 }
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/pom.xml b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/pom.xml
index 5053b919d9..ecc34f58f8 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/pom.xml
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-plugins-handlers</artifactId>
         <groupId>io.gravitee.am.plugins.handlers</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-plugins-handlers-certificate</artifactId>
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/pom.xml b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/pom.xml
index d09e07df37..dd630f22ac 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/pom.xml
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-plugins-handlers</artifactId>
         <groupId>io.gravitee.am.plugins.handlers</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-plugins-handlers-extensiongrant</artifactId>
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/pom.xml b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/pom.xml
index 0124db161c..36cbcaf404 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/pom.xml
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-plugins-handlers</artifactId>
         <groupId>io.gravitee.am.plugins.handlers</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-plugins-handlers-identityprovider</artifactId>
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-policy/pom.xml b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-policy/pom.xml
index 1260b178f7..ce73b9a805 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-policy/pom.xml
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-policy/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <artifactId>gravitee-am-plugins-handlers</artifactId>
         <groupId>io.gravitee.am.plugins.handlers</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/pom.xml b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/pom.xml
index a3bab968fb..6cb7f71269 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/pom.xml
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-plugins-handlers</artifactId>
         <groupId>io.gravitee.am.plugins.handlers</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-plugins-handlers-protocol</artifactId>
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/pom.xml b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/pom.xml
index b3c9cfa192..ac9154e5d1 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/pom.xml
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-plugins-handlers</artifactId>
         <groupId>io.gravitee.am.plugins.handlers</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-plugins-handlers-reporter</artifactId>
diff --git a/gravitee-am-plugins-handlers/pom.xml b/gravitee-am-plugins-handlers/pom.xml
index 8f41a2cbe9..a0dc9ea25c 100644
--- a/gravitee-am-plugins-handlers/pom.xml
+++ b/gravitee-am-plugins-handlers/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>gravitee-am-parent</artifactId>
         <groupId>io.gravitee.am</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.plugins.handlers</groupId>
diff --git a/gravitee-am-reporter/gravitee-am-reporter-api/pom.xml b/gravitee-am-reporter/gravitee-am-reporter-api/pom.xml
index 5e313ad559..345e152607 100644
--- a/gravitee-am-reporter/gravitee-am-reporter-api/pom.xml
+++ b/gravitee-am-reporter/gravitee-am-reporter-api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-reporter</artifactId>
         <groupId>io.gravitee.am.reporter</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-reporter-api</artifactId>
diff --git a/gravitee-am-reporter/gravitee-am-reporter-mongodb/pom.xml b/gravitee-am-reporter/gravitee-am-reporter-mongodb/pom.xml
index 8a063e2824..2dcf7153e0 100644
--- a/gravitee-am-reporter/gravitee-am-reporter-mongodb/pom.xml
+++ b/gravitee-am-reporter/gravitee-am-reporter-mongodb/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>gravitee-am-reporter</artifactId>
         <groupId>io.gravitee.am.reporter</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-reporter-mongodb</artifactId>
diff --git a/gravitee-am-reporter/pom.xml b/gravitee-am-reporter/pom.xml
index 68ab630e51..5d871a1ac9 100644
--- a/gravitee-am-reporter/pom.xml
+++ b/gravitee-am-reporter/pom.xml
@@ -24,14 +24,14 @@
     <parent>
         <artifactId>gravitee-am-parent</artifactId>
         <groupId>io.gravitee.am</groupId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.reporter</groupId>
     <artifactId>gravitee-am-reporter</artifactId>
     <packaging>pom</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Reporter</name>
 
     <modules>
diff --git a/gravitee-am-repository/gravitee-am-repository-api/pom.xml b/gravitee-am-repository/gravitee-am-repository-api/pom.xml
index eeedc1683d..d700563984 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/pom.xml
+++ b/gravitee-am-repository/gravitee-am-repository-api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.repository</groupId>
         <artifactId>gravitee-am-repository</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-repository-api</artifactId>
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/pom.xml b/gravitee-am-repository/gravitee-am-repository-mongodb/pom.xml
index ddda358366..351948bbe0 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/pom.xml
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>io.gravitee.am.repository</groupId>
         <artifactId>gravitee-am-repository</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-repository-mongodb</artifactId>
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 54104da58e..972cb23d1e 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
@@ -234,6 +234,7 @@ public class MongoClientRepository extends AbstractManagementMongoRepository imp
         client.setScopeApprovals((Map)clientMongo.getScopeApprovals());
         client.setAccountSettings(convert(clientMongo.getAccountSettings()));
         client.setTokenCustomClaims(getTokenClaims(clientMongo.getTokenCustomClaims()));
+        client.setTemplate(clientMongo.isTemplate());
         return client;
     }
 
@@ -299,6 +300,7 @@ public class MongoClientRepository extends AbstractManagementMongoRepository imp
         clientMongo.setScopeApprovals(client.getScopeApprovals() != null ? new Document((Map)client.getScopeApprovals()) : new Document());
         clientMongo.setAccountSettings(convert(client.getAccountSettings()));
         clientMongo.setTokenCustomClaims(getMongoTokenClaims(client.getTokenCustomClaims()));
+        clientMongo.setTemplate(client.isTemplate());
         return clientMongo;
     }
 
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java
index a21cc6e4ea..3e0c9f7697 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java
@@ -229,6 +229,7 @@ public class MongoDomainRepository extends AbstractManagementMongoRepository imp
         result.setDefaultScopes(dcrMongo.getDefaultScopes());
         result.setAllowedScopesEnabled(dcrMongo.isAllowedScopesEnabled());
         result.setAllowedScopes(dcrMongo.getAllowedScopes());
+        result.setClientTemplateEnabled(dcrMongo.isClientTemplateEnabled());
 
         return result;
     }
@@ -258,6 +259,7 @@ public class MongoDomainRepository extends AbstractManagementMongoRepository imp
         result.setDefaultScopes(dcr.getDefaultScopes());
         result.setAllowedScopesEnabled(dcr.isAllowedScopesEnabled());
         result.setAllowedScopes(dcr.getAllowedScopes());
+        result.setClientTemplateEnabled(dcr.isClientTemplateEnabled());
 
         return result;
     }
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ClientMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ClientMongo.java
index a432fa25f6..19b3986c8a 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ClientMongo.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ClientMongo.java
@@ -148,6 +148,8 @@ public class ClientMongo extends Auditable {
 
     private List<TokenClaimMongo> tokenCustomClaims;
 
+    private boolean template;
+
     public String getId() {
         return id;
     }
@@ -580,6 +582,14 @@ public class ClientMongo extends Auditable {
         this.tokenCustomClaims = tokenCustomClaims;
     }
 
+    public boolean isTemplate() {
+        return template;
+    }
+
+    public void setTemplate(boolean template) {
+        this.template = template;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/oidc/ClientRegistrationSettingsMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/oidc/ClientRegistrationSettingsMongo.java
index 5bd4ea7c2c..9dd4f9bd70 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/oidc/ClientRegistrationSettingsMongo.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/oidc/ClientRegistrationSettingsMongo.java
@@ -31,6 +31,7 @@ public class ClientRegistrationSettingsMongo {
     private List<String> defaultScopes;
     private boolean isAllowedScopesEnabled;
     private List<String> allowedScopes;
+    private boolean isClientTemplateEnabled;
 
 
     public boolean isAllowLocalhostRedirectUri() {
@@ -96,4 +97,12 @@ public class ClientRegistrationSettingsMongo {
     public void setAllowedScopes(List<String> allowedScopes) {
         this.allowedScopes = allowedScopes;
     }
+
+    public boolean isClientTemplateEnabled() {
+        return isClientTemplateEnabled;
+    }
+
+    public void setClientTemplateEnabled(boolean clientTemplateEnabled) {
+        isClientTemplateEnabled = clientTemplateEnabled;
+    }
 }
diff --git a/gravitee-am-repository/pom.xml b/gravitee-am-repository/pom.xml
index fd907a0253..b4d347630c 100644
--- a/gravitee-am-repository/pom.xml
+++ b/gravitee-am-repository/pom.xml
@@ -23,14 +23,14 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
 
     <groupId>io.gravitee.am.repository</groupId>
     <artifactId>gravitee-am-repository</artifactId>
     <packaging>pom</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Repository</name>
 
     <modules>
diff --git a/gravitee-am-service/pom.xml b/gravitee-am-service/pom.xml
index 9fd091b539..b99cb11798 100644
--- a/gravitee-am-service/pom.xml
+++ b/gravitee-am-service/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>io.gravitee.am</groupId>
         <artifactId>gravitee-am-parent</artifactId>
-        <version>2.8.0-SNAPSHOT</version>
+        <version>2.9.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
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 dc3f23bea7..0f26ce95fd 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
@@ -27,6 +27,7 @@ import java.util.List;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 public interface EmailTemplateService {
@@ -41,6 +42,8 @@ public interface EmailTemplateService {
 
     Maybe<Email> findByDomainAndClientAndTemplate(String domain, String client, String template);
 
+    Single<List<Email>> copyFromClient(String domain, String clientSource, String clientTarget);
+
     Single<Email> create(String domain, NewEmail newEmail, User principal);
 
     Single<Email> create(String domain, String client, NewEmail newEmail, User principal);
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 46bd72ed64..877e05df12 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
@@ -27,6 +27,7 @@ import java.util.List;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 public interface FormService {
@@ -39,6 +40,8 @@ public interface FormService {
 
     Maybe<Form> findByDomainAndClientAndTemplate(String domain, String client, String template);
 
+    Single<List<Form>> copyFromClient(String domain, String clientSource, String clientTarget);
+
     Single<Form> create(String domain, NewForm form, User principal);
 
     Single<Form> create(String domain, String client, NewForm form, User principal);
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 d82dd671fc..dfad4066da 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
@@ -19,6 +19,7 @@ import io.gravitee.am.common.audit.EventType;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.model.Email;
+import io.gravitee.am.model.Template;
 import io.gravitee.am.model.common.event.Action;
 import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.model.common.event.Payload;
@@ -36,6 +37,7 @@ import io.gravitee.am.service.model.UpdateEmail;
 import io.gravitee.am.service.reporter.builder.AuditBuilder;
 import io.gravitee.am.service.reporter.builder.management.EmailTemplateAuditBuilder;
 import io.reactivex.Completable;
+import io.reactivex.Flowable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import org.slf4j.Logger;
@@ -48,6 +50,7 @@ import java.util.List;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 @Component
@@ -118,6 +121,24 @@ public class EmailTemplateServiceImpl implements EmailTemplateService {
                 });
     }
 
+    @Override
+    public Single<List<Email>> copyFromClient(String domain, String clientSource, String clientTarget) {
+        return findByDomainAndClient(domain, clientSource)
+                .flatMapPublisher(Flowable::fromIterable)
+                .flatMapSingle(source -> {
+                    NewEmail email = new NewEmail();
+                    email.setEnabled(source.isEnabled());
+                    email.setTemplate(Template.parse(source.getTemplate()));
+                    email.setFrom(source.getFrom());
+                    email.setFromName(source.getFromName());
+                    email.setSubject(source.getSubject());
+                    email.setContent(source.getContent());
+                    email.setExpiresAfter(source.getExpiresAfter());
+                    return this.create(domain,clientTarget,email);
+                })
+                .toList();
+    }
+
     @Override
     public Single<Email> create(String domain, NewEmail newEmail, User principal) {
         LOGGER.debug("Create a new email {} for domain {}", newEmail, domain);
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 59b3f283ab..6f8bafb107 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
@@ -19,6 +19,7 @@ import io.gravitee.am.common.audit.EventType;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.model.Form;
+import io.gravitee.am.model.Template;
 import io.gravitee.am.model.common.event.Action;
 import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.model.common.event.Payload;
@@ -36,6 +37,7 @@ import io.gravitee.am.service.model.UpdateForm;
 import io.gravitee.am.service.reporter.builder.AuditBuilder;
 import io.gravitee.am.service.reporter.builder.management.FormTemplateAuditBuilder;
 import io.reactivex.Completable;
+import io.reactivex.Flowable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import org.slf4j.Logger;
@@ -48,6 +50,7 @@ import java.util.List;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 @Component
@@ -108,6 +111,21 @@ public class FormServiceImpl implements FormService {
                 });
     }
 
+    @Override
+    public Single<List<Form>> copyFromClient(String domain, String clientSource, String clientTarget) {
+        return findByDomainAndClient(domain, clientSource)
+                .flatMapPublisher(Flowable::fromIterable)
+                .flatMapSingle(source -> {
+                    NewForm form = new NewForm();
+                    form.setEnabled(source.isEnabled());
+                    form.setTemplate(Template.parse(source.getTemplate()));
+                    form.setContent(source.getContent());
+                    form.setAssets(source.getAssets());
+                    return this.create(domain,clientTarget,form);
+                })
+                .toList();
+    }
+
     @Override
     public Single<Form> create(String domain, NewForm newForm, User principal) {
         LOGGER.debug("Create a new form {} for domain {}", newForm, domain);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java
index 2a8e78b1f7..732670a1bf 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.service.impl;
 
 import io.gravitee.am.common.oauth2.Parameters;
+import io.gravitee.am.common.oidc.StandardClaims;
 import io.gravitee.am.common.oidc.idtoken.Claims;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.model.User;
@@ -313,6 +314,7 @@ public class UserServiceImpl implements UserService {
         if (additionalInformation != null) {
             Map<String, Object> extraInformation = new HashMap<>(additionalInformation);
             extraInformation.put(Claims.auth_time, user.getLoggedAt());
+            extraInformation.put(StandardClaims.PREFERRED_USERNAME, user.getUsername());
             user.setSource((String) extraInformation.remove(SOURCE_FIELD));
             user.setClient((String) extraInformation.remove(Parameters.CLIENT_ID));
             extraInformation.remove(GROUP_MAPPING_ATTRIBUTE);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchClient.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchClient.java
index c05fd2a1cc..36629857aa 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchClient.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchClient.java
@@ -79,6 +79,7 @@ public class PatchClient {
     private Optional<Map<String, Integer>> scopeApprovals;
     private Optional<AccountSettings> accountSettings;
     private Optional<List<TokenClaim>> tokenCustomClaims;
+    private Optional<Boolean> template;
 
     public Optional<List<String>> getRedirectUris() {
         return redirectUris;
@@ -480,6 +481,14 @@ public class PatchClient {
         this.tokenCustomClaims = tokenCustomClaims;
     }
 
+    public Optional<Boolean> getTemplate() {
+        return template;
+    }
+
+    public void setTemplate(Optional<Boolean> template) {
+        this.template = template;
+    }
+
     public Client patch(Client _toPatch, boolean forceNull) {
         // create new object for audit purpose (patch json result)
         Client toPatch = new Client(_toPatch);
@@ -539,6 +548,7 @@ public class PatchClient {
         SetterUtils.safeSet(toPatch::setScopeApprovals, this.getScopeApprovals());
         SetterUtils.safeSet(toPatch::setAccountSettings, this.getAccountSettings());
         SetterUtils.safeSet(toPatch::setTokenCustomClaims, this.getTokenCustomClaims());
+        SetterUtils.safeSet(toPatch::setTemplate, this.getTemplate(), boolean.class);
 
         return toPatch;
     }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateClient.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateClient.java
index 2707888689..f95681f411 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateClient.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateClient.java
@@ -22,6 +22,7 @@ import java.util.Set;
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
  * @author GraviteeSource Team
+ * @deprecated Please use PatchClient instead.
  */
 public class UpdateClient {
 
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchClientRegistrationSettings.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchClientRegistrationSettings.java
index 3fb05eeb11..a3f5f8e15c 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchClientRegistrationSettings.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchClientRegistrationSettings.java
@@ -70,6 +70,11 @@ public class PatchClientRegistrationSettings {
      */
     private Optional<List<String>> allowedScopes;
 
+    /**
+     * Enable client to be used as template for dynamic client registration
+     */
+    private Optional<Boolean> isClientTemplateEnabled;
+
     public Optional<Boolean> getAllowLocalhostRedirectUri() {
         return allowLocalhostRedirectUri;
     }
@@ -134,6 +139,14 @@ public class PatchClientRegistrationSettings {
         this.allowedScopes = allowedScopes;
     }
 
+    public Optional<Boolean> isClientTemplateEnabled() {
+        return isClientTemplateEnabled;
+    }
+
+    public void setClientTemplateEnabled(Optional<Boolean> clientTemplateEnabled) {
+        this.isClientTemplateEnabled = clientTemplateEnabled;
+    }
+
     public ClientRegistrationSettings patch(ClientRegistrationSettings toPatch) {
         ClientRegistrationSettings result=toPatch!=null?toPatch: ClientRegistrationSettings.defaultSettings();
 
@@ -143,8 +156,9 @@ public class PatchClientRegistrationSettings {
         SetterUtils.safeSet(result::setOpenDynamicClientRegistrationEnabled, this.isOpenDynamicClientRegistrationEnabled(), boolean.class);
         SetterUtils.safeSet(result::setDynamicClientRegistrationEnabled, this.isDynamicClientRegistrationEnabled(), boolean.class);
         SetterUtils.safeSet(result::setDefaultScopes, this.getDefaultScopes());
-        SetterUtils.safeSet(result::setAllowedScopesEnabled, this.isAllowedScopesEnabled, boolean.class);
+        SetterUtils.safeSet(result::setAllowedScopesEnabled, this.isAllowedScopesEnabled(), boolean.class);
         SetterUtils.safeSet(result::setAllowedScopes, this.getAllowedScopes());
+        SetterUtils.safeSet(result::setClientTemplateEnabled, this.isClientTemplateEnabled(), boolean.class);
 
         return result;
     }
diff --git a/gravitee-am-ui/build.json b/gravitee-am-ui/build.json
index b9d979295e..737adfe493 100644
--- a/gravitee-am-ui/build.json
+++ b/gravitee-am-ui/build.json
@@ -1,3 +1,3 @@
 {
-    "version": "2.8.0-SNAPSHOT"
+    "version": "2.8.0"
 }
diff --git a/gravitee-am-ui/package.json b/gravitee-am-ui/package.json
index 0550282adb..a6f29d10f6 100644
--- a/gravitee-am-ui/package.json
+++ b/gravitee-am-ui/package.json
@@ -1,6 +1,6 @@
 {
   "name": "gravitee-am-webui",
-  "version": "2.8.0-SNAPSHOT",
+  "version": "2.8.0",
   "license": "Apache-2.0",
   "scripts": {
     "ng": "ng",
diff --git a/gravitee-am-ui/pom.xml b/gravitee-am-ui/pom.xml
index de7a223162..6600a4e5a7 100644
--- a/gravitee-am-ui/pom.xml
+++ b/gravitee-am-ui/pom.xml
@@ -28,12 +28,12 @@
   <parent>
     <groupId>io.gravitee.am</groupId>
     <artifactId>gravitee-am-parent</artifactId>
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
   </parent>
 
   <groupId>io.gravitee.am</groupId>
   <artifactId>gravitee-am-webui</artifactId>
-  <version>2.8.0-SNAPSHOT</version>
+  <version>2.9.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <name>Gravitee.io AM - Portal</name>
 
diff --git a/gravitee-am-ui/src/app/app-routing.module.ts b/gravitee-am-ui/src/app/app-routing.module.ts
index 3dfca57143..7df2ed01e1 100644
--- a/gravitee-am-ui/src/app/app-routing.module.ts
+++ b/gravitee-am-ui/src/app/app-routing.module.ts
@@ -412,7 +412,7 @@ const routes: Routes = [
         },
         children: [
           { path: '', redirectTo: 'settings', pathMatch: 'full' },
-          { path: 'settings', component: ClientSettingsComponent },
+          { path: 'settings', component: ClientSettingsComponent, resolve: { domain: DomainResolver } },
           { path: 'idp', component: ClientIdPComponent },
           { path: 'oauth2', component: ClientOAuth2Component, resolve: { domainGrantTypes: ExtensionGrantsResolver, scopes: ScopesResolver } },
           { path: 'forms', component: ClientFormsComponent, resolve: { domain: DomainResolver } },
diff --git a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html
index 23c535f78a..f7a79ca102 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html
+++ b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html
@@ -63,6 +63,20 @@
       </mat-form-field>
     </div>
 
+    <div class="client-warning-zone" fxLayout="column">
+      <h4>Client template</h4>
+      <div fxFlex>
+        <mat-slide-toggle
+          matTooltip="{{isDomainDcrTemplateDisabled() ? 'Dynamic Client Registration template is disabled on domain' : ''}}"
+          color="warn"
+          [disabled]="isDomainDcrTemplateDisabled()"
+          (change)="enableTemplate($event)"
+          [checked]="client.template">
+        </mat-slide-toggle>
+      </div>
+      <mat-hint style="font-size: 75%;">If enabled, this client will serve as template for dynamic client registration and will no longer be usable for anything else.</mat-hint>
+    </div>
+
     <div fxLayout="row" class="client-settings-form-actions">
       <button mat-raised-button [disabled]="(!clientForm.valid || clientForm.pristine) && !formChanged" type="submit">SAVE</button>
     </div>
diff --git a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.scss b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.scss
index f0503307af..0d09c39980 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.scss
+++ b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.scss
@@ -67,3 +67,14 @@ mat-icon.delete-chip {
     margin: 5px 0;
   }
 }
+
+.client-warning-zone {
+  padding: 10px;
+  background-color: #f1f1f1;
+  margin-top: 20px;
+  h4 {
+    margin: 5px 0;
+    font-weight: normal;
+    color: #f44336;
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.ts b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.ts
index 123007d6fd..eb4e3ede7d 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.ts
+++ b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.ts
@@ -35,6 +35,7 @@ export interface Scope {
 export class ClientSettingsComponent implements OnInit {
   @ViewChild('dynamic', { read: ViewContainerRef }) viewContainerRef: ViewContainerRef;
   private domainId: string;
+  domain: any;
   client: any;
   formChanged: boolean = false;
   identityProviders: any[] = [];
@@ -50,7 +51,8 @@ export class ClientSettingsComponent implements OnInit {
   }
 
   ngOnInit() {
-    this.domainId = this.route.snapshot.parent.parent.params['domainId'];
+    this.domain = this.route.snapshot.data['domain'];
+    this.domainId = this.domain.id;
     this.client = this.route.snapshot.parent.data['client'];
     this.providerService.findByDomain(this.domainId).subscribe(data => this.identityProviders = data);
     this.certificateService.findByDomain(this.domainId).subscribe(data => this.certificates = data);
@@ -66,6 +68,15 @@ export class ClientSettingsComponent implements OnInit {
     this.formChanged = true;
   }
 
+  enableTemplate(event) {
+    this.client.template = event.checked;
+    this.formChanged = true;
+  }
+
+  isDomainDcrTemplateDisabled() {
+    return !this.domain.oidc.clientRegistrationSettings.isClientTemplateEnabled;
+  }
+
   isAutoApprove() {
     return this.client.autoApproveScopes && this.client.autoApproveScopes.indexOf('true') > -1;
   }
diff --git a/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.html b/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.html
index f16799a425..2976ed2005 100644
--- a/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.html
@@ -38,6 +38,14 @@
           </mat-slide-toggle>
           <mat-hint>Enable application owner to self register their application without needing to be authenticated.</mat-hint>
         </div>
+        <div class="domain-client-registration-settings-state" fxLayout="column">
+          <mat-slide-toggle
+            (change)="enableDynamicClientRegistrationTemplate($event)"
+            [checked]="domain.oidc.clientRegistrationSettings.isClientTemplateEnabled">
+            Enable/Disable Dynamic Client Registration Templates
+          </mat-slide-toggle>
+          <mat-hint>Enable application owner to use an existing client application as template when registering a new application.</mat-hint>
+        </div>
         <div class="domain-client-registration-settings-state" fxLayout="column">
           <mat-slide-toggle
             (change)="allowLocalhostRedirectUri($event)"
diff --git a/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts b/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts
index 50c9ec7105..37d68ee834 100644
--- a/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts
@@ -63,6 +63,11 @@ export class ClientRegistrationSettingsComponent implements OnInit {
     this.formChanged = true;
   }
 
+  enableDynamicClientRegistrationTemplate(event) {
+    this.domain.oidc.clientRegistrationSettings.isClientTemplateEnabled = event.checked;
+    this.formChanged = true;
+  }
+
   allowLocalhostRedirectUri(event) {
     this.domain.oidc.clientRegistrationSettings.allowLocalhostRedirectUri = event.checked;
     this.formChanged = true;
diff --git a/gravitee-am-ui/src/app/domain/settings/users/user/applications/application/application.component.ts b/gravitee-am-ui/src/app/domain/settings/users/user/applications/application/application.component.ts
index 4a98c157ba..8d7174ea52 100644
--- a/gravitee-am-ui/src/app/domain/settings/users/user/applications/application/application.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/users/user/applications/application/application.component.ts
@@ -61,7 +61,6 @@ export class UserApplicationComponent implements OnInit {
   }
 
   revokeConsent(event, consent) {
-    console.log(consent);
     event.preventDefault();
     this.dialogService
       .confirm('Revoke access', 'Are you sure you want to revoke this permission ?')
diff --git a/gravitee-am-ui/src/app/services/client.service.ts b/gravitee-am-ui/src/app/services/client.service.ts
index b6fc0808a6..ddde73219b 100644
--- a/gravitee-am-ui/src/app/services/client.service.ts
+++ b/gravitee-am-ui/src/app/services/client.service.ts
@@ -57,7 +57,8 @@ export class ClientService {
       'certificate': client.certificate,
       'enhanceScopesWithUserPermissions' : client.enhanceScopesWithUserPermissions,
       'responseTypes' : client.responseTypes,
-      'scopeApprovals' : client.scopeApprovals
+      'scopeApprovals' : client.scopeApprovals,
+      'template' : client.template
     });
   }
 
diff --git a/pom.xml b/pom.xml
index da890ca4be..18337d5dc3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,7 +30,7 @@
     <artifactId>gravitee-am-parent</artifactId>
     <packaging>pom</packaging>
 
-    <version>2.8.0-SNAPSHOT</version>
+    <version>2.9.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management</name>
 
     <scm>
@@ -53,7 +53,7 @@
         <gravitee-expression-language.version>1.2.0</gravitee-expression-language.version>
         <spring-security.version>4.2.4.RELEASE</spring-security.version>
         <jetty.version>9.4.19.v20190610</jetty.version>
-        <jjwt.version>0.10.6</jjwt.version>
+        <jjwt.version>0.10.7</jjwt.version>
         <rxjava.version>2.1.12</rxjava.version>
         <com.fasterxml.jackson.version>2.9.6</com.fasterxml.jackson.version>
         <nimbus.version>7.1</nimbus.version>
diff --git a/postman/collections/graviteeio-am-client-management-collection.json b/postman/collections/graviteeio-am-client-management-collection.json
index ec4b7cc701..46f8fc3d15 100644
--- a/postman/collections/graviteeio-am-client-management-collection.json
+++ b/postman/collections/graviteeio-am-client-management-collection.json
@@ -812,6 +812,61 @@
 							},
 							"response": []
 						},
+						{
+							"name": "Template - client_credentials",
+							"event": [
+								{
+									"listen": "test",
+									"script": {
+										"id": "c37cba72-91c2-4850-80cd-6fd3908e406e",
+										"exec": [
+											"pm.test(\"Status code is 200\", function () {",
+											"    pm.response.to.have.status(200);",
+											"});",
+											"",
+											"pm.test(\"is server\", function () {",
+											"    var body = JSON.parse(responseBody);",
+											"    pm.expect(body).to.have.property('template');",
+											"    pm.expect(body.template).to.eql(true);",
+											"});",
+											""
+										],
+										"type": "text/javascript"
+									}
+								}
+							],
+							"request": {
+								"method": "PUT",
+								"header": [
+									{
+										"key": "Content-Type",
+										"value": "application/json"
+									},
+									{
+										"key": "Authorization",
+										"value": "Bearer {{token}}"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"redirectUris\": [],\n  \"authorizedGrantTypes\": [\"client_credentials\"],\n  \"responseTypes\": [],\n  \"applicationType\": \"server\",\n  \"scopes\": [\"openid\"],\n  \"template\": true\n}"
+								},
+								"url": {
+									"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{client}}",
+									"host": [
+										"{{management_url}}"
+									],
+									"path": [
+										"management",
+										"domains",
+										"{{domain}}",
+										"clients",
+										"{{client}}"
+									]
+								}
+							},
+							"response": []
+						},
 						{
 							"name": "Delete client",
 							"event": [
diff --git a/postman/collections/graviteeio-am-oauth2-collection.json b/postman/collections/graviteeio-am-oauth2-collection.json
index 0aba5d2531..57f50a89fd 100644
--- a/postman/collections/graviteeio-am-oauth2-collection.json
+++ b/postman/collections/graviteeio-am-oauth2-collection.json
@@ -1,6 +1,6 @@
 {
 	"info": {
-		"_postman_id": "f1638c92-0d2b-41d4-8afc-1a38aac10217",
+		"_postman_id": "d6320ec7-d908-4f9e-82e4-a2416163a06b",
 		"name": "Gravitee.io - AM - Oauth2",
 		"description": "Test Oauth2 (RFC 6749) specifications",
 		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
@@ -659,6 +659,10 @@
 					"request": {
 						"method": "GET",
 						"header": [],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
 						"url": {
 							"raw": "{{gateway_url}}/{{domain}}/oidc/.well-known/openid-configuration",
 							"host": [
@@ -2060,6 +2064,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&scope=unknown-scope",
 											"host": [
@@ -2125,6 +2133,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -2257,6 +2269,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -2302,6 +2318,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -2383,6 +2403,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -2444,6 +2468,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -2580,6 +2608,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -2628,6 +2660,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -2751,6 +2787,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -2797,6 +2837,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -2982,6 +3026,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -3043,6 +3091,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3179,6 +3231,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3234,6 +3290,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3365,6 +3425,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3418,6 +3482,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -3487,6 +3555,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3533,6 +3605,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -3668,6 +3744,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -3729,6 +3809,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3863,6 +3947,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -3976,6 +4064,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -4057,6 +4149,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -4118,6 +4214,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -4252,6 +4352,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -4372,6 +4476,10 @@
 												"disabled": true
 											}
 										],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -4453,6 +4561,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876",
 											"host": [
@@ -4518,6 +4630,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -4653,6 +4769,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -4770,6 +4890,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -4851,6 +4975,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876",
 											"host": [
@@ -4916,6 +5044,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -5051,6 +5183,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -5104,6 +5240,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client&redirect_uri=http://localhost:4000/&state=9876-5678-1234",
 											"host": [
@@ -5169,6 +5309,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -5300,6 +5444,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -5346,6 +5494,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -5427,6 +5579,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256",
 											"host": [
@@ -5500,6 +5656,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -5635,6 +5795,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -5749,6 +5913,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -5830,6 +5998,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=plain",
 											"host": [
@@ -5903,6 +6075,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -6038,6 +6214,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -6152,6 +6332,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -6233,6 +6417,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256",
 											"host": [
@@ -6306,6 +6494,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -6441,6 +6633,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -6559,6 +6755,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -6640,6 +6840,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=plain",
 											"host": [
@@ -6713,6 +6917,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -6848,6 +7056,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -6966,6 +7178,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -7047,6 +7263,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876&code_challenge=qjrzSW9gMiUgpUvqgEPE4_-8swvyCtfOVvg55o5S_es&code_challenge_method=S256",
 											"host": [
@@ -7120,6 +7340,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -7255,6 +7479,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -7376,6 +7604,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -7457,6 +7689,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876&code_challenge=qjrzSW9gMiUgpUvqgEPE4_-8swvyCtfOVvg55o5S_es&code_challenge_method=plain",
 											"host": [
@@ -7530,6 +7766,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -7665,6 +7905,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -7786,6 +8030,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -7873,6 +8121,10 @@
 												"value": "Basic dW5rbm93bi1jbGllbnQ6dW5rbm93bi1zZWNyZXQ="
 											}
 										],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=unknown_response_type",
 											"host": [
@@ -7939,6 +8191,10 @@
 												"value": "Basic dW5rbm93bi1jbGllbnQ6dW5rbm93bi1zZWNyZXQ="
 											}
 										],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=unknown_response_type&response_type=unknown_response_type",
 											"host": [
@@ -8003,7 +8259,11 @@
 									"request": {
 										"method": "GET",
 										"header": [],
-										"url": {
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
+										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code",
 											"host": [
 												"{{authorizationEndpoint}}"
@@ -8063,6 +8323,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=unknownclient",
 											"host": [
@@ -8127,6 +8391,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client",
 											"host": [
@@ -8187,6 +8455,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client&redirect_uri=http://localhost:4000",
 											"host": [
@@ -8253,6 +8525,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://my_bad_host:4000",
 											"host": [
@@ -8320,6 +8596,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=code&client_id=my-client2&redirect_uri=http://my_bad_host:4000&state=xxx-yyy-zzz",
 											"host": [
@@ -8600,6 +8880,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=token",
 											"host": [
@@ -8664,6 +8948,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=token&client_id=my-client2&redirect_uri=http://localhost:4000/&scope=unknown-scope",
 											"host": [
@@ -8729,6 +9017,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -8862,6 +9154,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -8907,6 +9203,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -8988,6 +9288,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=token&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -9049,6 +9353,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -9183,6 +9491,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -9229,6 +9541,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -9310,6 +9626,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=token&client_id=my-client2&redirect_uri=http://localhost:4000/&state=1234-5678-9876",
 											"host": [
@@ -9375,6 +9695,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -9510,6 +9834,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -9556,6 +9884,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -9637,6 +9969,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=token&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -9698,6 +10034,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -9852,6 +10192,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -9952,6 +10296,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{authorizationEndpoint}}?response_type=token&client_id=my-client2&redirect_uri=http://localhost:4000/",
 											"host": [
@@ -10013,6 +10361,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -10138,6 +10490,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{redirection}}",
 											"host": [
@@ -10184,6 +10540,10 @@
 									"request": {
 										"method": "GET",
 										"header": [],
+										"body": {
+											"mode": "raw",
+											"raw": ""
+										},
 										"url": {
 											"raw": "{{logoutEndpoint}}",
 											"host": [
@@ -11293,6 +11653,229 @@
 						}
 					],
 					"_postman_isSubFolder": true
+				},
+				{
+					"name": "Template client",
+					"item": [
+						{
+							"name": "Create client template",
+							"event": [
+								{
+									"listen": "test",
+									"script": {
+										"id": "fbe2362d-ad75-4baa-8825-b74218b047c7",
+										"exec": [
+											"pm.test(\"Status code is 201\", function () {",
+											"    pm.response.to.have.status(201);",
+											"});",
+											"",
+											"var body = JSON.parse(responseBody);",
+											"pm.environment.set('templateId', body.id);"
+										],
+										"type": "text/javascript"
+									}
+								}
+							],
+							"request": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Content-Type",
+										"value": "application/json"
+									},
+									{
+										"key": "Authorization",
+										"value": "Bearer {{token}}"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"clientId\": \"my-client-template\",\n  \"clientSecret\": \"my-client-secret-template\"\n}"
+								},
+								"url": {
+									"raw": "{{management_url}}/management/domains/{{domain}}/clients",
+									"host": [
+										"{{management_url}}"
+									],
+									"path": [
+										"management",
+										"domains",
+										"{{domain}}",
+										"clients"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "Configure template - client_credentials",
+							"event": [
+								{
+									"listen": "test",
+									"script": {
+										"id": "58987251-6ffb-479d-80af-40373ea5a0cb",
+										"exec": [
+											"pm.test(\"Status code is 200\", function () {",
+											"    pm.response.to.have.status(200);",
+											"});",
+											"",
+											"pm.test(\"is server\", function () {",
+											"    var body = JSON.parse(responseBody);",
+											"    pm.expect(body).to.have.property('template');",
+											"    pm.expect(body.template).to.eql(true);",
+											"});",
+											"",
+											"// wait for sync process",
+											"setTimeout(function(){}, 5000);"
+										],
+										"type": "text/javascript"
+									}
+								}
+							],
+							"request": {
+								"method": "PATCH",
+								"header": [
+									{
+										"key": "Authorization",
+										"type": "text",
+										"value": "Bearer {{token}} "
+									},
+									{
+										"key": "Content-Type",
+										"name": "Content-Type",
+										"type": "text",
+										"value": "application/json"
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": "{\n  \"redirectUris\": [],\n  \"authorizedGrantTypes\": [\"client_credentials\"],\n  \"responseTypes\": [],\n  \"applicationType\": \"server\",\n  \"scopes\": [\"openid\"],\n  \"template\": true\n}"
+								},
+								"url": {
+									"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{templateId}}",
+									"host": [
+										"{{management_url}}"
+									],
+									"path": [
+										"management",
+										"domains",
+										"{{domain}}",
+										"clients",
+										"{{templateId}}"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "Template - Can not generate token",
+							"event": [
+								{
+									"listen": "test",
+									"script": {
+										"id": "462b93ec-a015-41b8-a1bf-8d5446fc31aa",
+										"exec": [
+											"pm.test(\"Status code is 401\", function () {",
+											"    pm.response.to.have.status(401);",
+											"});",
+											"",
+											"pm.test(\"Template can not be used.\", function () {",
+											"    var body = pm.response.json();",
+											"    pm.expect(body).to.have.property('error');",
+											"    pm.expect(body).to.have.property('error_description');",
+											"    ",
+											"    pm.expect(body.error).to.eql('invalid_client');",
+											"    pm.expect(body.error_description).to.eql('Client authentication failed due to unknown or invalid client');",
+											"});"
+										],
+										"type": "text/javascript"
+									}
+								},
+								{
+									"listen": "prerequest",
+									"script": {
+										"id": "904f0113-b2b9-4645-9511-48bb10791506",
+										"exec": [
+											""
+										],
+										"type": "text/javascript"
+									}
+								}
+							],
+							"request": {
+								"method": "POST",
+								"header": [
+									{
+										"key": "Authorization",
+										"value": "Basic bXktY2xpZW50LXRlbXBsYXRlOm15LWNsaWVudC1zZWNyZXQtdGVtcGxhdGU="
+									}
+								],
+								"body": {
+									"mode": "urlencoded",
+									"urlencoded": [
+										{
+											"key": "grant_type",
+											"value": "client_credentials",
+											"type": "text"
+										}
+									]
+								},
+								"url": {
+									"raw": "{{tokenEndpoint}}",
+									"host": [
+										"{{tokenEndpoint}}"
+									]
+								}
+							},
+							"response": []
+						},
+						{
+							"name": "Delete template",
+							"event": [
+								{
+									"listen": "test",
+									"script": {
+										"id": "58987251-6ffb-479d-80af-40373ea5a0cb",
+										"exec": [
+											"pm.test(\"Status code is 204\", function () {",
+											"    pm.response.to.have.status(204);",
+											"});"
+										],
+										"type": "text/javascript"
+									}
+								}
+							],
+							"request": {
+								"method": "DELETE",
+								"header": [
+									{
+										"key": "Authorization",
+										"type": "text",
+										"value": "Bearer {{token}} "
+									}
+								],
+								"body": {
+									"mode": "raw",
+									"raw": ""
+								},
+								"url": {
+									"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{templateId}}",
+									"host": [
+										"{{management_url}}"
+									],
+									"path": [
+										"management",
+										"domains",
+										"{{domain}}",
+										"clients",
+										"{{templateId}}"
+									]
+								}
+							},
+							"response": []
+						}
+					],
+					"_postman_isSubFolder": true
 				}
 			]
 		},
diff --git a/postman/collections/graviteeio-am-openid-dcr-collection.json b/postman/collections/graviteeio-am-openid-dcr-collection.json
index ac70cbee18..787b9dec8a 100644
--- a/postman/collections/graviteeio-am-openid-dcr-collection.json
+++ b/postman/collections/graviteeio-am-openid-dcr-collection.json
@@ -4848,6 +4848,824 @@
 				}
 			]
 		},
+		{
+			"name": "Case - Templates",
+			"item": [
+				{
+					"name": "Enable templating on domain",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "286df3a2-0e06-4b00-9614-3b0fd9ceecb9",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"DCR enabled for domain with templating\", function () {",
+									"    var jsonData = pm.response.json();",
+									"    pm.expect(jsonData.oidc.clientRegistrationSettings.isDynamicClientRegistrationEnabled).to.eql(true);",
+									"    pm.expect(jsonData.oidc.clientRegistrationSettings.isOpenDynamicClientRegistrationEnabled).to.eql(false);",
+									"    pm.expect(jsonData.oidc.clientRegistrationSettings.isClientTemplateEnabled).to.be.eql(true);",
+									"});",
+									"",
+									"// wait for sync process",
+									"setTimeout(function(){}, 5000);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"oidc\": {\n    \"clientRegistrationSettings\": {\n\t\t\"isClientTemplateEnabled\": true\n    }\n  }\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create client for templating",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "fbe2362d-ad75-4baa-8825-b74218b047c7",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"var body = JSON.parse(responseBody);",
+									"pm.environment.set('templateId', body.id);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"clientId\": \"template-client-id\",\n  \"clientSecret\": \"template-client-secret\",\n  \"clientName\": \"template-client-public-name\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Patch client to implicit template",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"// wait for sync process",
+									"setTimeout(function(){}, 5000);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"redirectUris\": [\"https://gravitee.io/callback\"],\n  \"authorizedGrantTypes\": [\"implicit\"],\n  \"responseTypes\": [\"token\"],\n  \"applicationType\": \"Single Page App\",\n  \"template\": true\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{templateId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{templateId}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create Login Form",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n\t\"template\":\"LOGIN\",\n\t\"enabled\":true,\n\t\"content\":\"// Custom template login form...\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{templateId}}/forms",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{templateId}}",
+								"forms"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create Error Form",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n\t\"template\":\"ERROR\",\n\t\"enabled\":true,\n\t\"content\":\"// Custom template error form...\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{templateId}}/forms",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{templateId}}",
+								"forms"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create Registration Mail",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n\t\"template\":\"REGISTRATION_CONFIRMATION\",\n\t\"expiresAfter\":86400,\n\t\"enabled\":true,\n\t\"from\":\"a@b.c\",\n\t\"fromName\":\"sender name\",\n\t\"subject\":\"abc\",\n\t\"content\":\"// Custom template email...\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{templateId}}/emails",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{templateId}}",
+								"emails"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get template endpoint from discovery",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "5332b6f5-7419-4792-a30f-cce968d3d67e",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check discovery endpoints\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    ",
+									"    pm.expect(body).to.have.property(\"registration_templates_endpoint\");",
+									"    pm.environment.set('registrationTemplatesEndpoint', body.registration_templates_endpoint);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{gateway_url}}/{{domain}}/oidc/.well-known/openid-configuration",
+							"host": [
+								"{{gateway_url}}"
+							],
+							"path": [
+								"{{domain}}",
+								"oidc",
+								".well-known",
+								"openid-configuration"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get template from discovery",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "5332b6f5-7419-4792-a30f-cce968d3d67e",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check discovery endpoints\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    pm.environment.set('software_id', body[0].software_id);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{registrationTemplatesEndpoint}}",
+							"host": [
+								"{{registrationTemplatesEndpoint}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Register client from template",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "d83b2401-3844-4147-bbb3-abe08b43a342",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"pm.test(\"Has template attributes values\", function () {",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.application_type).to.eql('Single Page App');",
+									"    pm.expect(body.response_types).to.eql(['token']);",
+									"    pm.expect(body.grant_types).to.eql(['implicit']);",
+									"",
+									"    //Has overrided metadata",
+									"    pm.expect(body.client_name).to.eql('New client from template');",
+									"    pm.expect(body.redirect_uris).to.eql(['https://client.example.org/callback']);",
+									"    ",
+									"    pm.expect(body).to.have.property(\"client_secret\");",
+									"    ",
+									"    pm.environment.set('newClientForDCRTests', body.id);",
+									"    pm.environment.set('registrationAccessToken', body.registration_access_token);",
+									"    pm.environment.set('registrationClientUri', body.registration_client_uri);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{access_token}}",
+								"type": "text"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n\t\"software_id\":\"{{software_id}}\",\n\t\"client_name\": \"New client from template\",\n\t\"redirect_uris\": [\"https://client.example.org/callback\"]\n}"
+						},
+						"url": {
+							"raw": "{{registrationEndpoint}}",
+							"host": [
+								"{{registrationEndpoint}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get Login Form",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Has values from template\", function () {",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.content).to.eql('// Custom template login form...');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{newClientForDCRTests}}/forms?template=LOGIN",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{newClientForDCRTests}}",
+								"forms"
+							],
+							"query": [
+								{
+									"key": "template",
+									"value": "LOGIN"
+								}
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get Error Form",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Has values from template\", function () {",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.content).to.eql('// Custom template error form...');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{newClientForDCRTests}}/forms?template=ERROR",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{newClientForDCRTests}}",
+								"forms"
+							],
+							"query": [
+								{
+									"key": "template",
+									"value": "ERROR"
+								}
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get User Consent Form",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 404\", function () {",
+									"    pm.response.to.have.status(404);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{newClientForDCRTests}}/forms?template=OAUTH2_USER_CONSENT",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{newClientForDCRTests}}",
+								"forms"
+							],
+							"query": [
+								{
+									"key": "template",
+									"value": "OAUTH2_USER_CONSENT"
+								}
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get Registration Email",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "1c063d6b-7120-4fd1-8aea-b04f7defc4d0",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Has values from template\", function () {",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.content).to.eql('// Custom template email...');",
+									"    pm.expect(body.from).to.eql('a@b.c');",
+									"    pm.expect(body.fromName).to.eql('sender name');",
+									"    pm.expect(body.subject).to.eql('abc');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{token}}"
+							},
+							{
+								"key": "Content-Type",
+								"name": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/clients/{{newClientForDCRTests}}/emails?template=REGISTRATION_CONFIRMATION",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"clients",
+								"{{newClientForDCRTests}}",
+								"emails"
+							],
+							"query": [
+								{
+									"key": "template",
+									"value": "REGISTRATION_CONFIRMATION"
+								}
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Check access with registration token",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "d83b2401-3844-4147-bbb3-abe08b43a342",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Get client with response types\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    pm.expect(body).to.have.property(\"redirect_uris\");",
+									"    pm.expect(body).to.have.property(\"grant_types\");",
+									"    pm.expect(body.response_types).to.eql([ 'token']);",
+									"    pm.expect(body.grant_types).to.eql([ 'implicit']);",
+									"    ",
+									"    //The Authorization Server need not include the registration_access_token or registration_client_uri value in this response unless they have been updated.",
+									"    pm.expect(body).to.not.have.property('registration_access_token');",
+									"    pm.expect(body).to.not.have.property('registration_client_uri');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{registrationAccessToken}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{registrationClientUri}}",
+							"host": [
+								"{{registrationClientUri}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Delete client",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "d83b2401-3844-4147-bbb3-abe08b43a342",
+								"exec": [
+									"pm.test(\"Status code is 204\", function () {",
+									"    pm.response.to.have.status(204);",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Content-Type",
+								"type": "text",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"type": "text",
+								"value": "Bearer {{registrationAccessToken}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{registrationClientUri}}",
+							"host": [
+								"{{registrationClientUri}}"
+							]
+						}
+					},
+					"response": []
+				}
+			]
+		},
 		{
 			"name": "Case - Signing id_token",
 			"item": [


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/client/ClientSyncServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/client/ClientSyncServiceTest.java
new file mode 100644
index 0000000000..98dce7319f
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/client/ClientSyncServiceTest.java
@@ -0,0 +1,310 @@
+/**
+ * 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.common.client;
+
+import io.gravitee.am.gateway.core.event.ClientEvent;
+import io.gravitee.am.gateway.handler.common.client.impl.ClientSyncServiceImpl;
+import io.gravitee.am.model.Client;
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.common.event.Action;
+import io.gravitee.am.model.common.event.Payload;
+import io.gravitee.am.repository.management.api.ClientRepository;
+import io.gravitee.common.event.Event;
+import io.gravitee.common.event.EventListener;
+import io.gravitee.common.event.EventManager;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ClientSyncServiceTest {
+
+    private static Set<Client> clientSet;
+
+    @InjectMocks
+    private ClientSyncService clientSyncService = new ClientSyncServiceImpl();
+
+    @Mock
+    private Domain domain;
+
+    @Mock
+    private ClientRepository clientRepository;
+
+    @BeforeClass
+    public static void initializeClients() {
+        clientSet = new HashSet<>();
+
+        Client domainAClientA = new Client();
+        domainAClientA.setId("aa");
+        domainAClientA.setDomain("domainA");
+        domainAClientA.setClientId("domainAClientA");
+
+        Client domainAClientB = new Client();
+        domainAClientB.setId("ab");
+        domainAClientB.setDomain("domainA");
+        domainAClientB.setClientId("domainAClientB");
+        domainAClientB.setTemplate(true);
+
+        Client domainAClientC = new Client();
+        domainAClientC.setId("ac");
+        domainAClientC.setDomain("domainA");
+        domainAClientC.setClientId("domainAClientC");
+        domainAClientC.setTemplate(true);
+
+        Client domainBClientA = new Client();
+        domainBClientA.setId("ba");
+        domainBClientA.setDomain("domainB");
+        domainBClientA.setClientId("domainBClientA");
+
+        Client domainBClientB = new Client();
+        domainBClientB.setId("bb");
+        domainBClientB.setDomain("domainB");
+        domainBClientB.setClientId("domainBClientB");
+        domainBClientB.setTemplate(true);
+
+        Client domainBClientC = new Client();
+        domainBClientC.setId("bc");
+        domainBClientC.setDomain("domainB");
+        domainBClientC.setClientId("domainBClientC");
+        domainBClientC.setTemplate(true);
+
+        clientSet.add(domainAClientA);
+        clientSet.add(domainAClientB);
+        clientSet.add(domainAClientC);
+        clientSet.add(domainBClientA);
+        clientSet.add(domainBClientB);
+        clientSet.add(domainBClientC);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        when(domain.getId()).thenReturn("domainA");
+        when(clientRepository.findAll()).thenReturn(Single.just(clientSet));
+        ((InitializingBean)this.clientSyncService).afterPropertiesSet();
+    }
+
+    @Test
+    public void findById_clientFound() {
+        TestObserver<Client> test = clientSyncService.findById("aa").test();
+        test.assertComplete().assertNoErrors();
+        test.assertValue(client -> client.getClientId().equals("domainAClientA"));
+    }
+
+    @Test
+    public void findById_clientNotFound() {
+        when(domain.getId()).thenReturn("domainB");
+        TestObserver<Client> test = clientSyncService.findById("aa").test();
+        test.assertComplete().assertNoErrors().assertNoValues();
+    }
+
+    @Test
+    public void findByClientId_clientFound() {
+        TestObserver<Client> test = clientSyncService.findByClientId("domainAClientA").test();
+        test.assertComplete().assertNoErrors();
+        test.assertValue(client -> client.getClientId().equals("domainAClientA"));
+    }
+
+    @Test
+    public void findByClientId_templateShouldNotBeThere() {
+        TestObserver<Client> test = clientSyncService.findByClientId("domainAClientB").test();
+        test.assertComplete().assertNoErrors().assertNoValues();
+    }
+
+    @Test
+    public void findTemplates() {
+        TestObserver<List<Client>> test = clientSyncService.findTemplates().test();
+        test.assertComplete().assertNoErrors();
+        test.assertValue(clients -> clients!=null && clients.size()==2);
+    }
+
+    @Test
+    public void addDynamicClientRegistred_ok() {
+        //Template should not added
+        Client existingClient = new Client();
+        existingClient.setId("aa");
+        existingClient.setDomain("domainA");
+        existingClient.setClientId("domainAClientA");
+
+        clientSyncService.addDynamicClientRegistred(existingClient);
+        TestObserver<Client> test = clientSyncService.findByClientId("domainAClientA").test();
+        test.assertComplete().assertNoErrors();
+        test.assertValue(client -> client.getClientId().equals("domainAClientA"));
+    }
+
+    @Test
+    public void addDynamicClientRegistred_ShouldRejectTemplate() {
+        //Template should not added
+        Client template = new Client();
+        template.setDomain("domainA");
+        template.setClientId("template");
+        template.setTemplate(true);
+
+        clientSyncService.addDynamicClientRegistred(template);
+        TestObserver test = clientSyncService.findByClientId("template").test();
+        test.assertComplete().assertNoErrors().assertNoValues();
+    }
+
+    @Test
+    public void removeDynamicClientRegistred() {
+        Client client = new Client();
+        client.setDomain("domainA");
+        client.setId("aa");
+
+        clientSyncService.removeDynamicClientRegistred(client);
+        TestObserver test = clientSyncService.findByClientId("domainAClientA").test();
+        test.assertComplete().assertNoErrors().assertNoValues();
+    }
+
+    @Test
+    public void onEvent_deploy() {
+        Client client = new Client();
+        client.setId("ad");
+        client.setDomain("domainA");
+        client.setClientId("domainAClientD");
+        when(clientRepository.findById("ad")).thenReturn(Maybe.just(client));
+
+        ((EventListener<ClientEvent, Payload>)clientSyncService).onEvent(new Event<ClientEvent, Payload>() {
+            @Override
+            public Payload content() {
+                return new Payload("ad","domainA", Action.CREATE);
+            }
+
+            @Override
+            public ClientEvent type() {
+                return ClientEvent.DEPLOY;
+            }
+        });
+
+        TestObserver<Client> test = clientSyncService.findById("ad").test();
+        test.assertComplete().assertNoErrors();
+        test.assertValue(c -> c.getClientId().equals("domainAClientD"));
+    }
+
+    @Test
+    public void onEvent_update() {
+        Client client = new Client();
+        client.setId("aa");
+        client.setDomain("domainA");
+        client.setClientId("domainAClientA");
+        client.setClientName("updatedClient");
+        when(clientRepository.findById("aa")).thenReturn(Maybe.just(client));
+
+        ((EventListener<ClientEvent, Payload>)clientSyncService).onEvent(new Event<ClientEvent, Payload>() {
+            @Override
+            public Payload content() {
+                return new Payload("aa","domainA", Action.UPDATE);
+            }
+
+            @Override
+            public ClientEvent type() {
+                return ClientEvent.UPDATE;
+            }
+        });
+
+        TestObserver<Client> test = clientSyncService.findById("aa").test();
+        test.assertComplete().assertNoErrors();
+        test.assertValue(c -> c.getClientName().equals("updatedClient"));
+    }
+
+    @Test
+    public void onEvent_update_template() {
+        Client existingClient = new Client();
+        existingClient.setId("aa");
+        existingClient.setDomain("domainA");
+        existingClient.setClientId("domainAClientA");
+        existingClient.setTemplate(true);
+        when(clientRepository.findById("aa")).thenReturn(Maybe.just(existingClient));
+
+        ((EventListener<ClientEvent, Payload>)clientSyncService).onEvent(new Event<ClientEvent, Payload>() {
+            @Override
+            public Payload content() {
+                return new Payload("aa","domainA", Action.UPDATE);
+            }
+
+            @Override
+            public ClientEvent type() {
+                return ClientEvent.UPDATE;
+            }
+        });
+
+        TestObserver<Client> test = clientSyncService.findById("aa").test();
+        test.assertComplete().assertNoErrors();
+        test.assertNoValues();
+
+        TestObserver<List<Client>> test2 = clientSyncService.findTemplates().test();
+        test2.assertComplete().assertNoErrors();
+        test2.assertValue(templates -> templates!=null && templates.size()==3);
+    }
+
+
+    @Test
+    public void onEvent_delete() {
+        ((EventListener<ClientEvent, Payload>)clientSyncService).onEvent(new Event<ClientEvent, Payload>() {
+            @Override
+            public Payload content() {
+                return new Payload("aa","domainA", Action.DELETE);
+            }
+
+            @Override
+            public ClientEvent type() {
+                return ClientEvent.UNDEPLOY;
+            }
+        });
+
+        TestObserver<Client> test = clientSyncService.findById("aa").test();
+        test.assertComplete().assertNoErrors().assertNoValues();
+    }
+
+    @Test
+    public void onEvent_delete_template() {
+        ((EventListener<ClientEvent, Payload>)clientSyncService).onEvent(new Event<ClientEvent, Payload>() {
+            @Override
+            public Payload content() {
+                return new Payload("ac","domainA", Action.DELETE);
+            }
+
+            @Override
+            public ClientEvent type() {
+                return ClientEvent.UNDEPLOY;
+            }
+        });
+
+        TestObserver<Client> test = clientSyncService.findById("ac").test();
+        test.assertComplete().assertNoErrors().assertNoValues();
+
+        TestObserver<List<Client>> test2 = clientSyncService.findTemplates().test();
+        test2.assertComplete().assertNoErrors();
+        test2.assertValue(templates -> templates!=null && templates.size()==1 && templates.get(0).getId().equals("ab"));
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpointTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpointTest.java
index bb70f653ae..223501341c 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpointTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientAccessEndpointTest.java
@@ -20,9 +20,7 @@ import io.gravitee.am.gateway.handler.common.vertx.RxWebTestBase;
 import io.gravitee.am.gateway.handler.oauth2.resources.handler.ExceptionHandler;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationService;
 import io.gravitee.am.model.Client;
-import io.gravitee.am.service.ClientService;
 import io.gravitee.common.http.HttpStatusCode;
-import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import io.vertx.core.http.HttpMethod;
@@ -42,9 +40,6 @@ import static org.mockito.Mockito.when;
 @RunWith(MockitoJUnitRunner.class)
 public class DynamicClientAccessEndpointTest extends RxWebTestBase {
 
-    @Mock
-    private ClientService clientService;
-
     @Mock
     private ClientSyncService clientSyncService;
 
@@ -52,7 +47,7 @@ public class DynamicClientAccessEndpointTest extends RxWebTestBase {
     private DynamicClientRegistrationService dcrService;
 
     @InjectMocks
-    DynamicClientAccessEndpoint endpoint = new DynamicClientAccessEndpoint(dcrService,clientService,clientSyncService);
+    DynamicClientAccessEndpoint endpoint = new DynamicClientAccessEndpoint(dcrService, clientSyncService);
 
     @Override
     public void setUp() throws Exception{
@@ -70,27 +65,26 @@ public class DynamicClientAccessEndpointTest extends RxWebTestBase {
         client.setClientId("my-test-client_id");
 
         when(clientSyncService.findByClientId("my-test-client_id")).thenReturn(Maybe.just(client));
+        when(clientSyncService.findByClientId("unknown")).thenReturn(Maybe.empty());
     }
 
     @Test
-    public void unknownClient() throws Exception{
-        when(clientSyncService.findByClientId("unknown-client_id")).thenReturn(Maybe.empty());
-
+    public void read() throws Exception{
         testRequest(
-                HttpMethod.GET, "/register/unknown-client_id",
-                HttpStatusCode.NOT_FOUND_404, "Not Found");
+                HttpMethod.GET, "/register/my-test-client_id",
+                HttpStatusCode.OK_200, "OK");
     }
 
     @Test
-    public void read() throws Exception{
+    public void read_notFound() throws Exception{
         testRequest(
-                HttpMethod.GET, "/register/my-test-client_id",
-                HttpStatusCode.OK_200, "OK");
+                HttpMethod.GET, "/register/unknown",
+                HttpStatusCode.NOT_FOUND_404, "Not Found");
     }
 
     @Test
     public void delete() throws Exception{
-        when(clientService.delete("my-test-client_id")).thenReturn(Completable.complete());
+        when(dcrService.delete(any())).thenReturn(Single.just(new Client()));
         when(clientSyncService.removeDynamicClientRegistred(any())).thenReturn(new Client());
 
         testRequest(
@@ -98,10 +92,16 @@ public class DynamicClientAccessEndpointTest extends RxWebTestBase {
                 HttpStatusCode.NO_CONTENT_204, "No Content");
     }
 
+    @Test
+    public void delete_notFound() throws Exception{
+        testRequest(
+                HttpMethod.DELETE, "/register/unknown",
+                HttpStatusCode.NOT_FOUND_404, "Not Found");
+    }
+
     @Test
     public void renewClientSecret() throws Exception{
-        when(dcrService.applyRegistrationAccessToken(any(),any())).thenReturn(Single.just(new Client()));
-        when(clientService.renewClientSecret(any())).thenReturn(Single.just(new Client()));
+        when(dcrService.renewSecret(any(), any())).thenReturn(Single.just(new Client()));
         when(clientSyncService.addDynamicClientRegistred(any())).thenReturn(new Client());
 
         testRequest(
@@ -109,6 +109,13 @@ public class DynamicClientAccessEndpointTest extends RxWebTestBase {
                 HttpStatusCode.OK_200, "OK");
     }
 
+    @Test
+    public void renewClientSecret_notFound() throws Exception{
+        testRequest(
+                HttpMethod.POST, "/register/unknown/renew_secret",
+                HttpStatusCode.NOT_FOUND_404, "Not Found");
+    }
+
     @Test
     public void patch_noBody() throws Exception{
         String body = "{\"redirect_uris\": [\"https://redirecturi\"]}";
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpointTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpointTest.java
index 4c1272044b..8df230ae34 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpointTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/resources/endpoint/DynamicClientRegistrationEndpointTest.java
@@ -19,7 +19,6 @@ import io.gravitee.am.gateway.handler.common.client.ClientSyncService;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationRequest;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.DynamicClientRegistrationService;
 import io.gravitee.am.model.Client;
-import io.gravitee.am.service.ClientService;
 import io.reactivex.Single;
 import io.vertx.core.json.DecodeException;
 import io.vertx.core.json.JsonObject;
@@ -32,7 +31,7 @@ import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import static org.mockito.Mockito.*;
 
@@ -43,9 +42,6 @@ import static org.mockito.Mockito.*;
 @RunWith(MockitoJUnitRunner.class)
 public class DynamicClientRegistrationEndpointTest {
 
-    @Mock
-    private ClientService clientService;
-
     @Mock
     private ClientSyncService clientSyncService;
 
@@ -53,7 +49,7 @@ public class DynamicClientRegistrationEndpointTest {
     private DynamicClientRegistrationService dcrService;
 
     @InjectMocks
-    private DynamicClientRegistrationEndpoint endpoint = new DynamicClientRegistrationEndpoint(dcrService, clientService, clientSyncService);
+    private DynamicClientRegistrationEndpoint endpoint = new DynamicClientRegistrationEndpoint(dcrService, clientSyncService);
 
     @Mock
     private DynamicClientRegistrationRequest request;
@@ -94,22 +90,12 @@ public class DynamicClientRegistrationEndpointTest {
         verify(routingContext, times(1)).fail(any());
     }
 
-    @Test
-    public void register_invalidRequest() {
-        //Context
-        when(dcrService.validateClientRegistrationRequest(any())).thenReturn(Single.error(new Exception()));
-
-        //Test
-        endpoint.handle(routingContext);
-
-        //Assertions
-        verify(routingContext, times(1)).fail(any());
-    }
-
     @Test
     public void register_fail() {
         //Context
-        when(dcrService.validateClientRegistrationRequest(any())).thenReturn(Single.just(request));
+        HttpServerRequest serverRequest = Mockito.mock(HttpServerRequest.class);
+        when(routingContext.request()).thenReturn(serverRequest);
+        when(dcrService.create(any(),any())).thenReturn(Single.error(new Exception()));
 
         //Test
         endpoint.handle(routingContext);
@@ -132,12 +118,7 @@ public class DynamicClientRegistrationEndpointTest {
         when(serverResponse.putHeader(anyString(),anyString())).thenReturn(serverResponse);
         when(serverResponse.setStatusCode(201)).thenReturn(serverResponse);
 
-        when(dcrService.create(any())).thenReturn(new Client());
-        when(dcrService.validateClientRegistrationRequest(any())).thenReturn(Single.just(request));
-        when(dcrService.applyDefaultIdentityProvider(any())).thenReturn(Single.just(new Client()));
-        when(dcrService.applyDefaultCertificateProvider(any())).thenReturn(Single.just(new Client()));
-        when(dcrService.applyRegistrationAccessToken(any(),any())).thenReturn(Single.just(new Client()));
-        when(clientService.create(any())).thenReturn(Single.just(new Client()));
+        when(dcrService.create(any(),any())).thenReturn(Single.just(new Client()));
         when(clientSyncService.addDynamicClientRegistred(any())).thenReturn(new Client());
 
         //Test
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationServiceTest.java
index e0705b30f5..f98f5edf86 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationServiceTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oidc/service/clientregistration/DynamicClientRegistrationServiceTest.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.gateway.handler.oidc.service.clientregistration;
 
+import io.gravitee.am.common.jwt.JWT;
 import io.gravitee.am.gateway.handler.common.jwk.JWKService;
 import io.gravitee.am.gateway.handler.common.jwt.JWTService;
 import io.gravitee.am.gateway.handler.oidc.service.clientregistration.impl.DynamicClientRegistrationServiceImpl;
@@ -27,9 +28,13 @@ import io.gravitee.am.model.IdentityProvider;
 import io.gravitee.am.model.oidc.JWKSet;
 import io.gravitee.am.model.oidc.OIDCSettings;
 import io.gravitee.am.service.CertificateService;
+import io.gravitee.am.service.ClientService;
+import io.gravitee.am.service.EmailTemplateService;
+import io.gravitee.am.service.FormService;
 import io.gravitee.am.service.IdentityProviderService;
 import io.gravitee.am.service.exception.InvalidClientMetadataException;
 import io.gravitee.am.service.exception.InvalidRedirectUriException;
+import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import io.reactivex.observers.TestObserver;
@@ -49,10 +54,10 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Optional;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 /**
  * @author Alexandre FARIA (contact at alexandrefaria.net)
@@ -67,6 +72,9 @@ public class DynamicClientRegistrationServiceTest {
     @Mock
     private OpenIDDiscoveryService openIDDiscoveryService;
 
+    @Mock
+    private OpenIDProviderMetadata openIDProviderMetadata;
+
     @Mock
     private IdentityProviderService identityProviderService;
 
@@ -83,122 +91,124 @@ public class DynamicClientRegistrationServiceTest {
     public WebClient webClient;
 
     @Mock
-    private Domain domain;
-
-    @Test
-    public void create() {
-        String domainId = "domain";
-        String clientName = "name";
+    public ClientService clientService;
 
-        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setClientName(Optional.of(clientName));
+    @Mock
+    private Domain domain;
 
-        when(domain.getId()).thenReturn(domainId);
-        Client result = dcrService.create(request);
-        assertTrue(result.getDomain().equals(domainId));
-        assertTrue(result.getClientName().equals(clientName));
-    }
+    @Mock
+    private FormService formService;
 
-    @Test
-    public void applyDefaultIdentiyProvider_noIdentityProvider() {
-        when(identityProviderService.findByDomain(any())).thenReturn(Single.just(Collections.emptyList()));
+    @Mock
+    private EmailTemplateService emailTemplateService;
+
+    private static final String DOMAIN_ID = "domain";
+    private static final String BASE_PATH = "";
+    private static final String ID_SOURCE = "123";
+    private static final String ID_TARGET = "abc";
+
+    @Before
+    public void setUp() {
+        when(domain.getId()).thenReturn(DOMAIN_ID);
+        when(identityProviderService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptyList()));
+        when(certificateService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptyList()));
+        when(openIDProviderMetadata.getRegistrationEndpoint()).thenReturn("https://issuer/register");
+        when(openIDDiscoveryService.getConfiguration(BASE_PATH)).thenReturn(openIDProviderMetadata);
+        when(openIDProviderMetadata.getIssuer()).thenReturn("https://issuer");
+        when(jwtService.encode(any(JWT.class),any(Client.class))).thenReturn(Single.just("jwt"));
 
-        TestObserver testObserver = dcrService.applyDefaultIdentityProvider(new Client()).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
-        testObserver.assertValue(client -> client!=null && ((Client)client).getIdentities()==null);
+        when(clientService.create(any())).thenAnswer(i -> {
+            Client res = i.getArgument(0);
+            res.setId(ID_TARGET);
+            return Single.just(res);
+        });
+        when(clientService.update(any())).thenAnswer(i -> Single.just(i.getArgument(0)));
+        when(clientService.delete(any())).thenReturn(Completable.complete());
+        when(clientService.renewClientSecret(any())).thenAnswer(i -> {
+            Client toRenew = i.getArgument(0);
+            toRenew.setClientSecret("secretRenewed");
+            return Single.just(toRenew);
+        });
     }
 
     @Test
-    public void applyDefaultIdentiyProvider() {
-        IdentityProvider identityProvider = Mockito.mock(IdentityProvider.class);
-        when(identityProvider.getId()).thenReturn("identity-provider-id-123");
-
-        when(identityProviderService.findByDomain(any())).thenReturn(Single.just(Arrays.asList(identityProvider)));
-
-        TestObserver testObserver = dcrService.applyDefaultIdentityProvider(new Client()).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
-        testObserver.assertValue(client -> client!=null && ((Client)client).getIdentities().contains("identity-provider-id-123"));
+    public void create_nullRequest() {
+        TestObserver<Client> testObserver = dcrService.create(null, BASE_PATH).test();
+        testObserver.assertError(InvalidClientMetadataException.class);
+        testObserver.assertErrorMessage("One of the Client Metadata value is invalid.");
+        testObserver.assertNotComplete();
     }
 
     @Test
-    public void applyDefaultCertificateProvider_noCertificateProvider() {
-        when(certificateService.findByDomain(any())).thenReturn(Single.just(Collections.emptyList()));
-
-        TestObserver testObserver = dcrService.applyDefaultCertificateProvider(new Client()).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
-        testObserver.assertValue(client -> client!=null && ((Client)client).getCertificate()==null);
+    public void create_missingRedirectUri() {
+        TestObserver<Client> testObserver = dcrService.create(new DynamicClientRegistrationRequest(), BASE_PATH).test();
+        testObserver.assertError(InvalidRedirectUriException.class);
+        testObserver.assertErrorMessage("Missing or invalid redirect_uris.");//redirect_uri metadata can be null but is mandatory
+        testObserver.assertNotComplete();
     }
 
     @Test
-    public void applyDefaultCertificateProvider_default() {
-        Certificate certificate = Mockito.mock(Certificate.class);
-        when(certificate.getId()).thenReturn("certificate-id-123");
-
-        when(certificateService.findByDomain(any())).thenReturn(Single.just(Arrays.asList(certificate)));
+    public void create_emptyRedirectUriArray() {
+        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setRedirectUris(Optional.of(Arrays.asList()));
 
-        TestObserver testObserver = dcrService.applyDefaultCertificateProvider(new Client()).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
-        testObserver.assertValue(client -> client!=null && ((Client)client).getCertificate().equals("certificate-id-123"));
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
+        testObserver.assertComplete().assertNoErrors();
+        testObserver.assertValue(client -> client.getRedirectUris().isEmpty());
     }
 
     @Test
-    public void applyRegistrationAccessToken() {
-        OpenIDProviderMetadata openIDProviderMetadata = Mockito.mock(OpenIDProviderMetadata.class);
-        when(jwtService.encode(any(),any(Client.class))).thenReturn(Single.just("token"));
-        when(openIDDiscoveryService.getConfiguration(any())).thenReturn(openIDProviderMetadata);
-        when(openIDProviderMetadata.getIssuer()).thenReturn("https://issuer");
-        when(openIDProviderMetadata.getRegistrationEndpoint()).thenReturn("https://issuer/register");
+    public void create_defaultCase() {
+        String clientName = "name";
 
-        TestObserver testObserver = dcrService.applyRegistrationAccessToken(any(),new Client()).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
-        testObserver.assertValue(client -> client!=null && ((Client)client).getRegistrationAccessToken().equals("token"));
-    }
+        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setClientName(Optional.of(clientName));
+        request.setRedirectUris(Optional.empty());
 
-    @Test
-    public void validateClientRegistrationRequest_nullRequest() {
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(null).test();
-        testObserver.assertError(InvalidClientMetadataException.class);
-        testObserver.assertErrorMessage("One of the Client Metadata value is invalid.");
-        testObserver.assertNotComplete();
+        TestObserver<Client> testObserver = dcrService.create(request,BASE_PATH).test();
+        testObserver.assertComplete().assertNoErrors();
+        testObserver.assertValue(client -> this.defaultAssertion(client) &&
+                client.getClientName().equals(clientName) &&
+                client.getIdentities() == null &&
+                client.getCertificate() == null
+        );
+        verify(clientService, times(1)).create(any());
     }
 
     @Test
-    public void validateClientRegistrationRequest_nullRedirectUriRequest() {
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(new DynamicClientRegistrationRequest()).test();
-        testObserver.assertError(InvalidRedirectUriException.class);
-        testObserver.assertNotComplete();
-    }
+    public void create_applyDefaultIdentiyProvider() {
+        IdentityProvider identityProvider = Mockito.mock(IdentityProvider.class);
+        when(identityProvider.getId()).thenReturn("identity-provider-id-123");
+        when(identityProviderService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Arrays.asList(identityProvider)));
 
-    @Test
-    public void validateClientRegistrationRequest_emptyRedirectUriRequest() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        //Allow null values on redirect_uris (ex client_credentials...)
-        request.setRedirectUris(Optional.empty());//"redirect_uris": null
+        request.setRedirectUris(Optional.empty());
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> defaultAssertion(client) && client.getIdentities().contains("identity-provider-id-123"));
     }
 
     @Test
-    public void validateClientRegistrationRequest_emptyArrayRedirectUriRequest() {
+    public void create_applyDefaultCertificateProvider() {
+        Certificate certificate = Mockito.mock(Certificate.class);
+        when(certificate.getId()).thenReturn("certificate-id-123");
+        when(certificateService.findByDomain(any())).thenReturn(Single.just(Arrays.asList(certificate)));
+
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setRedirectUris(Optional.empty());
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> defaultAssertion(client) && client.getCertificate().equals("certificate-id-123"));
     }
 
     @Test
-    public void validateClientRegistrationRequest_notAllowedScopes() {
+    public void create_notAllowedScopes() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setRedirectUris(Optional.empty());
         request.setScope(Optional.of("not allowed"));
 
         OIDCSettings oidc = OIDCSettings.defaultSettings();
@@ -206,17 +216,17 @@ public class DynamicClientRegistrationServiceTest {
         oidc.getClientRegistrationSettings().setAllowedScopes(Arrays.asList("openid","profile"));
         when(domain.getOidc()).thenReturn(oidc);
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
         //scope is not allowed, so expecting to erase scope (no scope)
-        testObserver.assertValue(o -> !((DynamicClientRegistrationRequest)o).getScope().isPresent());
+        testObserver.assertValue(client -> this.defaultAssertion(client) && client.getScopes()==null);
     }
 
     @Test
-    public void validateClientRegistrationRequest_notAllowedScopes_defaultScopes() {
+    public void create_notAllowedScopes_defaultScopes() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setRedirectUris(Optional.empty());
         request.setScope(Optional.of("not allowed"));
 
         OIDCSettings oidc = OIDCSettings.defaultSettings();
@@ -225,17 +235,19 @@ public class DynamicClientRegistrationServiceTest {
         oidc.getClientRegistrationSettings().setDefaultScopes(Arrays.asList("phone","email"));
         when(domain.getOidc()).thenReturn(oidc);
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().size()==2);
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().containsAll(Arrays.asList("phone","email")));
+        testObserver.assertValue(client -> this.defaultAssertion(client) &&
+                client.getScopes().size() == 2 &&
+                client.getScopes().containsAll(Arrays.asList("phone","email"))
+        );
     }
 
     @Test
-    public void validateClientRegistrationRequest_filteredScopes() {
+    public void create_filteredScopes() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setRedirectUris(Optional.empty());
         request.setScope(Optional.of("openid not allowed"));
 
         OIDCSettings oidc = OIDCSettings.defaultSettings();
@@ -243,222 +255,229 @@ public class DynamicClientRegistrationServiceTest {
         oidc.getClientRegistrationSettings().setAllowedScopes(Arrays.asList("openid","profile"));
         when(domain.getOidc()).thenReturn(oidc);
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().size()==1);
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().get(0).equals("openid"));
+        testObserver.assertValue(client -> this.defaultAssertion(client) &&
+                client.getScopes().size() == 1 &&
+                client.getScopes().containsAll(Arrays.asList("openid"))
+        );
     }
 
     @Test
-    public void validateClientRegistrationRequest_defaultScopes_notUsed() {
+    public void create_defaultScopes_notUsed() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setRedirectUris(Optional.empty());
         request.setScope(Optional.of("openid not allowed"));
 
         OIDCSettings oidc = OIDCSettings.defaultSettings();
         oidc.getClientRegistrationSettings().setDefaultScopes(Arrays.asList("phone","email"));
         when(domain.getOidc()).thenReturn(oidc);
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().size()==3);
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().containsAll(Arrays.asList("openid","not","allowed")));
+        testObserver.assertValue(client -> this.defaultAssertion(client) &&
+                client.getScopes().size() == 3 &&
+                client.getScopes().containsAll(Arrays.asList("openid","not","allowed"))
+        );
     }
 
     @Test
-    public void validateClientRegistrationRequest_defaultScopes() {
+    public void create_defaultScopes() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setRedirectUris(Optional.empty());
 
         OIDCSettings oidc = OIDCSettings.defaultSettings();
         oidc.getClientRegistrationSettings().setDefaultScopes(Arrays.asList("phone","email"));
         when(domain.getOidc()).thenReturn(oidc);
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().size()==2);
-        testObserver.assertValue(o -> ((DynamicClientRegistrationRequest)o).getScope().get().containsAll(Arrays.asList("phone","email")));
+        testObserver.assertValue(client -> this.defaultAssertion(client) &&
+                client.getScopes().size() == 2 &&
+                client.getScopes().containsAll(Arrays.asList("phone","email"))
+        );
     }
 
     @Test
-    public void validateClientRegistrationRequest_unknownResponseTypePayload() {
+    public void create_emptyResponseTypePayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
-        request.setResponseTypes(Optional.of(Arrays.asList("unknownResponseType")));
+        request.setRedirectUris(Optional.empty());
+        request.setResponseTypes(Optional.empty());
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
-        testObserver.assertError(InvalidClientMetadataException.class);
-        testObserver.assertErrorMessage("Invalid response type.");
-        testObserver.assertNotComplete();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
+        testObserver.assertNoErrors();
+        testObserver.assertComplete();
+        testObserver.assertValue(client -> client.getResponseTypes()==null);
     }
 
     @Test
-    public void validateClientRegistrationRequest_ok_emptyResponseTypePayload() {
+    public void create_unknownResponseTypePayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
-        request.setResponseTypes(Optional.empty());
+        request.setRedirectUris(Optional.empty());
+        request.setResponseTypes(Optional.of(Arrays.asList("unknownResponseType")));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
+        testObserver.assertError(InvalidClientMetadataException.class);
+        testObserver.assertErrorMessage("Invalid response type.");
+        testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unknownGrantTypePayload() {
+    public void create_unknownGrantTypePayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setGrantTypes(Optional.of(Arrays.asList("unknownGrantType")));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Missing or invalid grant type.");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedSubjectTypePayload() {
+    public void create_unsupportedSubjectTypePayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setSubjectType(Optional.of("unknownSubjectType"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported subject type");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedUserinfoSigningAlgorithmPayload() {
+    public void create_unsupportedUserinfoSigningAlgorithmPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setUserinfoSignedResponseAlg(Optional.of("unknownSigningAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported userinfo signing algorithm");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedUserinfoResponseAlgPayload() {
+    public void create_unsupportedUserinfoResponseAlgPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setUserinfoEncryptedResponseAlg(Optional.of("unknownEncryptionAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported userinfo_encrypted_response_alg value");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_missingUserinfoResponseAlgPayload() {
+    public void create_missingUserinfoResponseAlgPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setUserinfoEncryptedResponseEnc(Optional.of("unknownEncryptionAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("When userinfo_encrypted_response_enc is included, userinfo_encrypted_response_alg MUST also be provided");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedUserinfoResponseEncPayload() {
+    public void create_unsupportedUserinfoResponseEncPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setUserinfoEncryptedResponseAlg(Optional.of("RSA-OAEP-256"));
         request.setUserinfoEncryptedResponseEnc(Optional.of("unknownEncryptionAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported userinfo_encrypted_response_enc value");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_defaultUserinfoResponseEncPayload() {
+    public void create_defaultUserinfoResponseEncPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setUserinfoEncryptedResponseAlg(Optional.of("RSA-OAEP-256"));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
-        testObserver.assertValue(result -> result.getUserinfoEncryptedResponseEnc()!=null);
+        testObserver.assertValue(client -> defaultAssertion(client) && client.getUserinfoEncryptedResponseEnc()!=null);
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedIdTokenSigningAlgorithmPayload() {
+    public void create_unsupportedIdTokenSigningAlgorithmPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setIdTokenSignedResponseAlg(Optional.of("unknownSigningAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported id_token signing algorithm");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedIdTokenResponseAlgPayload() {
+    public void create_unsupportedIdTokenResponseAlgPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setIdTokenEncryptedResponseAlg(Optional.of("unknownEncryptionAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported id_token_encrypted_response_alg value");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_missingIdTokenResponseAlgPayload() {
+    public void create_missingIdTokenResponseAlgPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setIdTokenEncryptedResponseEnc(Optional.of("unknownEncryptionAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("When id_token_encrypted_response_enc is included, id_token_encrypted_response_alg MUST also be provided");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_unsupportedIdTokenResponseEncPayload() {
+    public void create_unsupportedIdTokenResponseEncPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setIdTokenEncryptedResponseAlg(Optional.of("RSA-OAEP-256"));
         request.setIdTokenEncryptedResponseEnc(Optional.of("unknownEncryptionAlg"));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("Unsupported id_token_encrypted_response_enc value");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_defaultIdTokenResponseEncPayload() {
+    public void create_defaultIdTokenResponseEncPayload() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setIdTokenEncryptedResponseAlg(Optional.of("RSA-OAEP-256"));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
-        testObserver.assertValue(result -> result.getIdTokenEncryptedResponseEnc()!=null);
+        testObserver.assertValue(client -> defaultAssertion(client) && client.getIdTokenEncryptedResponseEnc()!=null);
     }
 
     @Test
-    public void validateClientRegistrationRequest_unvalidRequestUris() {
+    public void create_invalidRequestUris() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setRequestUris(Optional.of(Arrays.asList("nonValidUri")));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertNotComplete();
         assertTrue("Should have only one exception", testObserver.errorCount()==1);
@@ -466,24 +485,25 @@ public class DynamicClientRegistrationServiceTest {
     }
 
     @Test
-    public void validateClientRegistrationRequest_validRequestUris() {
+    public void create_validRequestUris() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setRequestUris(Optional.of(Arrays.asList("https://valid/request/uri")));
 
-        TestObserver testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> defaultAssertion(client) && client.getRequestUris().contains("https://valid/request/uri"));
     }
 
 
     @Test
-    public void validateClientRegistrationRequest_sectorIdentifierUriBadFormat() {
+    public void create_sectorIdentifierUriBadFormat() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setSectorIdentifierUri(Optional.of("blabla"));//fail due to invalid url
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertNotComplete();
         assertTrue("Should have only one exception", testObserver.errorCount()==1);
@@ -491,12 +511,12 @@ public class DynamicClientRegistrationServiceTest {
     }
 
     @Test
-    public void validateClientRegistrationRequest_sectorIdentifierUriNottHttps() {
+    public void create_sectorIdentifierUriNottHttps() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setSectorIdentifierUri(Optional.of("http://something"));//fail due to invalid url
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertNotComplete();
         assertTrue("Should have only one exception", testObserver.errorCount()==1);
@@ -504,10 +524,10 @@ public class DynamicClientRegistrationServiceTest {
     }
 
     @Test
-    public void validateClientRegistrationRequest_sectorIdentifierUriBadRequest() {
+    public void create_sectorIdentifierUriBadRequest() {
         final String sectorUri = "https://sector/uri";
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setSectorIdentifierUri(Optional.of(sectorUri));//fail due to invalid url
         HttpRequest<Buffer> httpRequest = Mockito.mock(HttpRequest.class);
         HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
@@ -515,7 +535,7 @@ public class DynamicClientRegistrationServiceTest {
         when(webClient.getAbs(sectorUri)).thenReturn(httpRequest);
         when(httpRequest.rxSend()).thenReturn(Single.just(httpResponse));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertNotComplete();
         assertTrue("Should have only one exception", testObserver.errorCount()==1);
@@ -523,7 +543,7 @@ public class DynamicClientRegistrationServiceTest {
     }
 
     @Test
-    public void validateClientRegistrationRequest_sectorIdentifierUri_invalidRedirectUri() {
+    public void create_sectorIdentifierUri_invalidRedirectUri() {
         final String sectorUri = "https://sector/uri";
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
         request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
@@ -535,18 +555,18 @@ public class DynamicClientRegistrationServiceTest {
         when(httpRequest.rxSend()).thenReturn(Single.just(httpResponse));
         when(httpResponse.bodyAsString()).thenReturn("[\"https://not/same/redirect/uri\"]");
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidRedirectUriException.class);
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_sectorIdentifierUri_validRedirectUri() {
+    public void create_sectorIdentifierUri_validRedirectUri() {
         final String redirectUri = "https://graviee.io/callback";
         final String sectorUri = "https://sector/uri";
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
         request.setRedirectUris(Optional.of(Arrays.asList(redirectUri)));
-        request.setSectorIdentifierUri(Optional.of(sectorUri));//fail due to invalid url
+        request.setSectorIdentifierUri(Optional.of(sectorUri));
         HttpRequest<Buffer> httpRequest = Mockito.mock(HttpRequest.class);
         HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
 
@@ -554,109 +574,240 @@ public class DynamicClientRegistrationServiceTest {
         when(httpRequest.rxSend()).thenReturn(Single.just(httpResponse));
         when(httpResponse.bodyAsString()).thenReturn("[\""+redirectUri+"\"]");
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_validateJWKsDuplicatedSource() {
+    public void create_validateJWKsDuplicatedSource() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setJwks(Optional.of(new JWKSet()));
         request.setJwksUri(Optional.of("something"));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("The jwks_uri and jwks parameters MUST NOT be used together.");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_validateJWKsUriWithoutJwkSet() {
+    public void create_validateJWKsUriWithoutJwkSet() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setJwksUri(Optional.of("something"));
 
         when(jwkService.getKeys(anyString())).thenReturn(Maybe.empty());
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertError(InvalidClientMetadataException.class);
         testObserver.assertErrorMessage("No JWK found behind jws uri...");
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validateClientRegistrationRequest_validateJWKsUriOk() {
+    public void create_validateJWKsUriOk() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setRedirectUris(Optional.empty());
         request.setJwksUri(Optional.of("something"));
 
         when(jwkService.getKeys(anyString())).thenReturn(Maybe.just(new JWKSet()));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
+        testObserver.assertNoErrors();
+        testObserver.assertComplete();
+        testObserver.assertValue(client -> defaultAssertion(client) && client.getJwksUri().equals("something"));
+    }
+
+    @Test
+    public void create_client_credentials() {
+        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setRedirectUris(Optional.empty());
+        request.setGrantTypes(Optional.of(Arrays.asList("client_credentials")));
+        request.setResponseTypes(Optional.of(Arrays.asList()));
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> client.getRedirectUris()==null &&
+                client.getAuthorizedGrantTypes().size()==1 &&
+                client.getAuthorizedGrantTypes().contains("client_credentials") &&
+                client.getResponseTypes().isEmpty()
+        );
     }
 
     @Test
-    public void validateClientRegistrationRequest_ok() {
+    public void create_implicit() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
         request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setGrantTypes(Optional.of(Arrays.asList("implicit")));
+        request.setResponseTypes(Optional.of(Arrays.asList("token")));
+
+        TestObserver<Client> testObserver = dcrService.create(request, BASE_PATH).test();
+        testObserver.assertNoErrors();
+        testObserver.assertComplete();
+        testObserver.assertValue(client -> client.getRedirectUris().contains("https://graviee.io/callback") &&
+                client.getAuthorizedGrantTypes().size() == 1 &&
+                client.getAuthorizedGrantTypes().contains("implicit") &&
+                client.getResponseTypes().size() == 1 &&
+                client.getResponseTypes().contains("token")
+        );
+    }
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+    private boolean defaultAssertion(Client client) {
+        assertNotNull("Client is null",client);
+        assertNotNull("Client id is null", client.getClientId());
+
+        assertNull("expecting no redirect_uris", client.getRedirectUris());
+
+        assertEquals("Domain is wrong",DOMAIN_ID,client.getDomain());
+        assertEquals("registration uri is wrong", "https://issuer/register"+"/"+client.getClientId(), client.getRegistrationClientUri());
+        assertEquals("registration token is wrong", "jwt", client.getRegistrationAccessToken());
+        assertEquals("should be default value \"web\"", "web", client.getApplicationType());
+
+        assertTrue("should be default value \"code\"", client.getResponseTypes().size()==1 && client.getResponseTypes().contains("code"));
+        return true;
+    }
+
+    @Test
+    public void patch_noRedirectUriMetadata() {
+        TestObserver<Client> testObserver = dcrService.patch(new Client(), new DynamicClientRegistrationRequest(), BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        verify(clientService, times(1)).update(any());
     }
 
     @Test
-    public void validateClientRegistrationRequest_client_credentials_ok() {
+    public void patch() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList()));
-        request.setGrantTypes(Optional.of(Arrays.asList("client_credentials")));
-        request.setResponseTypes(Optional.of(Arrays.asList()));
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientRegistrationRequest(request).test();
+        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
+        request.setJwksUri(Optional.of("something"));
+
+        when(jwkService.getKeys(anyString())).thenReturn(Maybe.just(new JWKSet()));
+
+        TestObserver<Client> testObserver = dcrService.patch(new Client(), request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> client.getJwksUri().equals("something") && client.getRedirectUris().size()==1);
+        verify(clientService, times(1)).update(any());
     }
 
     @Test
-    public void validatePatchRequest_nullRequest() {
-        TestObserver testObserver = dcrService.validateClientPatchRequest(null).test();
-        testObserver.assertError(InvalidClientMetadataException.class);
-        testObserver.assertErrorMessage("One of the Client Metadata value is invalid.");
+    public void update_missingRedirectUri() {
+        TestObserver<Client> testObserver = dcrService.update(new Client(), new DynamicClientRegistrationRequest(), BASE_PATH).test();
+        testObserver.assertError(InvalidRedirectUriException.class);
+        testObserver.assertErrorMessage("Missing or invalid redirect_uris.");//redirect_uri metadata can be null but is mandatory
         testObserver.assertNotComplete();
     }
 
     @Test
-    public void validatePatchRequest_emptyRequest() {
+    public void update() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setRedirectUris(Optional.of(Arrays.asList()));
+        request.setApplicationType(Optional.of("something"));
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientPatchRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.update(new Client(), request, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> client.getApplicationType().equals("something") && client.getRedirectUris().isEmpty());
+        verify(clientService, times(1)).update(any());
     }
 
     @Test
-    public void validatePatchRequest_emptyRedirectUri() {
-        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.empty());
+    public void delete() {
+        TestObserver<Client> testObserver = dcrService.delete(new Client()).test();
+        testObserver.assertNoErrors();
+        testObserver.assertComplete();
+        verify(clientService, times(1)).delete(any());
+    }
+
+    @Test
+    public void renewSecret() {
+        Client toRenew = new Client();
+        toRenew.setClientId("client_id");
+        toRenew.setClientSecret("oldSecret");
 
-        TestObserver testObserver = dcrService.validateClientPatchRequest(request).test();
+        TestObserver<Client> testObserver = dcrService.renewSecret(toRenew, BASE_PATH).test();
         testObserver.assertNoErrors();
         testObserver.assertComplete();
+        testObserver.assertValue(client -> client.getClientSecret().equals("secretRenewed") && client.getRegistrationClientUri()!=null);
+        verify(clientService, times(1)).renewClientSecret(any());
     }
 
     @Test
-    public void validatePatchRequest() {
+    public void createFromTemplate_templateNotFound() {
         DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
-        request.setRedirectUris(Optional.of(Arrays.asList("https://graviee.io/callback")));
-        request.setJwksUri(Optional.of("something"));
+        request.setSoftwareId(Optional.of("123"));
 
-        when(jwkService.getKeys(anyString())).thenReturn(Maybe.just(new JWKSet()));
+        when(domain.isDynamicClientRegistrationTemplateEnabled()).thenReturn(true);
+        when(clientService.findById(any())).thenReturn(Maybe.empty());
 
-        TestObserver<DynamicClientRegistrationRequest> testObserver = dcrService.validateClientPatchRequest(request).test();
-        testObserver.assertNoErrors();
-        testObserver.assertComplete();
+        TestObserver<Client> testObserver = dcrService.create(request,BASE_PATH).test();
+        testObserver.assertNotComplete();
+        testObserver.assertError(InvalidClientMetadataException.class);
+        testObserver.assertErrorMessage("No template found for software_id 123");
+        verify(clientService, times(0)).create(any());
+    }
+
+    @Test
+    public void createFromTemplate_isNotTemplate() {
+        Client template = new Client();
+        template.setId("123");
+        template.setClientName("shouldBeRemoved");
+        template.setClientId("shouldBeReplaced");
+        template.setClientSecret("shouldBeRemoved");
+        template.setRedirectUris(Arrays.asList("shouldBeRemoved"));
+        template.setSectorIdentifierUri("shouldBeRemoved");
+        template.setJwks(new JWKSet());
+
+        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setSoftwareId(Optional.of("123"));
+        request.setApplicationType(Optional.of("app"));
+
+        when(domain.isDynamicClientRegistrationTemplateEnabled()).thenReturn(true);
+        when(clientService.findById("123")).thenReturn(Maybe.just(template));
+
+        TestObserver<Client> testObserver = dcrService.create(request,BASE_PATH).test();
+        testObserver.assertNotComplete();
+        testObserver.assertError(InvalidClientMetadataException.class);
+        testObserver.assertErrorMessage("Client behind software_id is not a template");
+        verify(clientService, times(0)).create(any());
+    }
+
+    @Test
+    public void createFromTemplate() {
+        Client template = new Client();
+        template.setId(ID_SOURCE);
+        template.setClientName("shouldBeRemoved");
+        template.setClientId("shouldBeReplaced");
+        template.setClientSecret("shouldBeRemoved");
+        template.setRedirectUris(Arrays.asList("shouldBeRemoved"));
+        template.setSectorIdentifierUri("shouldBeRemoved");
+        template.setJwks(new JWKSet());
+        template.setTemplate(true);
+
+        DynamicClientRegistrationRequest request = new DynamicClientRegistrationRequest();
+        request.setSoftwareId(Optional.of(ID_SOURCE));
+        request.setApplicationType(Optional.of("app"));
+
+        when(formService.copyFromClient(DOMAIN_ID, ID_SOURCE, ID_TARGET)).thenReturn(Single.just(Collections.emptyList()));
+        when(emailTemplateService.copyFromClient(DOMAIN_ID, ID_SOURCE, ID_TARGET)).thenReturn(Single.just(Collections.emptyList()));
+        when(domain.isDynamicClientRegistrationTemplateEnabled()).thenReturn(true);
+        when(clientService.findById("123")).thenReturn(Maybe.just(template));
+
+        TestObserver<Client> testObserver = dcrService.create(request,BASE_PATH).test();
+        testObserver.assertComplete().assertNoErrors();
+        testObserver.assertValue(client ->
+                client.getId().equals("abc") &&
+                client.getApplicationType().equals("app") &&
+                client.getClientId() != null &&
+                !client.getClientId().equals("shouldBeReplaced") &&
+                client.getRedirectUris() == null &&
+                client.getClientName() == null &&
+                client.getClientSecret() == null &&
+                client.getJwks() == null &&
+                client.getSectorIdentifierUri() == null
+        );
+        verify(clientService, times(1)).create(any());
     }
 }
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/EmailTemplateServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/EmailTemplateServiceTest.java
index 7f1e89ad00..ea9d0bd8cf 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/EmailTemplateServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/EmailTemplateServiceTest.java
@@ -35,16 +35,18 @@ import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.*;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 @RunWith(MockitoJUnitRunner.class)
@@ -242,4 +244,71 @@ public class EmailTemplateServiceTest {
         verify(emailRepository, times(1)).delete(email.getId());
         verify(domainService, times(1)).reload(eq(email.getDomain()), any());
     }
+
+    @Test
+    public void copyFromClient() {
+
+        final String sourceUid = "sourceUid";
+        final String targetUid = "targetUid";
+
+        Email mailOne = new Email();
+        mailOne.setId("templateId");
+        mailOne.setEnabled(true);
+        mailOne.setDomain(DOMAIN);
+        mailOne.setClient(sourceUid);
+        mailOne.setTemplate("login");
+        mailOne.setFrom("fromMail");
+        mailOne.setFromName("mailName");
+        mailOne.setSubject("mailSubject");
+        mailOne.setContent("mailContent");
+        mailOne.setExpiresAfter(0);
+
+        Email mailTwo = new Email(mailOne);
+        mailTwo.setTemplate("error");
+        mailTwo.setExpiresAfter(1000);
+
+        when(emailRepository.findByDomainAndClientAndTemplate(DOMAIN,targetUid,"login")).thenReturn(Maybe.empty());
+        when(emailRepository.findByDomainAndClientAndTemplate(DOMAIN,targetUid,"error")).thenReturn(Maybe.empty());
+        when(emailRepository.create(any())).thenAnswer(i -> Single.just(i.getArgument(0)));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+        when(emailRepository.findByDomainAndClient(DOMAIN, sourceUid)).thenReturn(Single.just(Arrays.asList(mailOne, mailTwo)));
+
+        TestObserver<List<Email>> testObserver = emailTemplateService.copyFromClient(DOMAIN, sourceUid, targetUid).test();
+        testObserver.assertComplete().assertNoErrors();
+        testObserver.assertValue(emails -> emails!=null && emails.size()==2 && emails.stream().filter(
+                email -> email.getDomain().equals(DOMAIN) &&
+                        email.getClient().equals(targetUid) &&
+                        !email.getId().equals("templateId") &&
+                        Arrays.asList("login","error").contains(email.getTemplate()) &&
+                        email.getFrom().equals("fromMail") &&
+                        email.getFromName().equals("mailName")
+                ).count()==2
+        );
+    }
+
+    @Test
+    public void copyFromClient_duplicateFound() {
+
+        final String sourceUid = "sourceUid";
+        final String targetUid = "targetUid";
+
+        Email mailOne = new Email();
+        mailOne.setId("templateId");
+        mailOne.setEnabled(true);
+        mailOne.setDomain(DOMAIN);
+        mailOne.setClient(sourceUid);
+        mailOne.setTemplate("login");
+        mailOne.setFrom("fromMail");
+        mailOne.setFromName("mailName");
+        mailOne.setSubject("mailSubject");
+        mailOne.setContent("mailContent");
+        mailOne.setExpiresAfter(0);
+
+        when(emailRepository.findByDomainAndClientAndTemplate(DOMAIN,targetUid,"login")).thenReturn(Maybe.just(new Email()));
+        when(emailRepository.findByDomainAndClient(DOMAIN, sourceUid)).thenReturn(Single.just(Arrays.asList(mailOne)));
+
+        TestObserver<List<Email>> testObserver = emailTemplateService.copyFromClient(DOMAIN, sourceUid, targetUid).test();
+        testObserver.assertNotComplete();
+        testObserver.assertError(EmailAlreadyExistsException.class);
+    }
 }
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/FormServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/FormServiceTest.java
new file mode 100644
index 0000000000..c706daf7b3
--- /dev/null
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/FormServiceTest.java
@@ -0,0 +1,117 @@
+/**
+ * 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.service;
+
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Form;
+import io.gravitee.am.repository.management.api.FormRepository;
+import io.gravitee.am.service.exception.FormAlreadyExistsException;
+import io.gravitee.am.service.impl.FormServiceImpl;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FormServiceTest {
+
+    @InjectMocks
+    private FormService formService = new FormServiceImpl();
+
+    @Mock
+    private FormRepository formRepository;
+
+    @Mock
+    private DomainService domainService;
+
+    @Mock
+    private AuditService auditService;
+
+    private static final String DOMAIN = "domain";
+
+    @Test
+    public void copyFromClient() {
+
+        final String sourceUid = "sourceUid";
+        final String targetUid = "targetUid";
+
+        Form formOne = new Form();
+        formOne.setId("templateId");
+        formOne.setEnabled(true);
+        formOne.setDomain(DOMAIN);
+        formOne.setClient(sourceUid);
+        formOne.setTemplate("login");
+        formOne.setContent("formContent");
+        formOne.setAssets("formAsset");
+
+        Form formTwo = new Form(formOne);
+        formTwo.setTemplate("error");
+
+        when(formRepository.findByDomainAndClientAndTemplate(DOMAIN,targetUid,"login")).thenReturn(Maybe.empty());
+        when(formRepository.findByDomainAndClientAndTemplate(DOMAIN,targetUid,"error")).thenReturn(Maybe.empty());
+        when(formRepository.create(any())).thenAnswer(i -> Single.just(i.getArgument(0)));
+        when(formRepository.findByDomainAndClient(DOMAIN, sourceUid)).thenReturn(Single.just(Arrays.asList(formOne, formTwo)));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver<List<Form>> testObserver = formService.copyFromClient(DOMAIN, sourceUid, targetUid).test();
+        testObserver.assertComplete().assertNoErrors();
+        testObserver.assertValue(forms -> forms!=null && forms.size()==2 && forms.stream().filter(
+                form -> form.getDomain().equals(DOMAIN) &&
+                        form.getClient().equals(targetUid) &&
+                        !form.getId().equals("templateId") &&
+                        Arrays.asList("login","error").contains(form.getTemplate()) &&
+                        form.getContent().equals("formContent") &&
+                        form.getAssets().equals("formAsset")
+                ).count()==2
+        );
+    }
+
+    @Test
+    public void copyFromClient_duplicateFound() {
+
+        final String sourceUid = "sourceUid";
+        final String targetUid = "targetUid";
+
+        Form formOne = new Form();
+        formOne.setId("templateId");
+        formOne.setEnabled(true);
+        formOne.setDomain(DOMAIN);
+        formOne.setClient(sourceUid);
+        formOne.setTemplate("login");
+        formOne.setContent("formContent");
+        formOne.setAssets("formAsset");
+
+        when(formRepository.findByDomainAndClientAndTemplate(DOMAIN,targetUid,"login")).thenReturn(Maybe.just(new Form()));
+        when(formRepository.findByDomainAndClient(DOMAIN, sourceUid)).thenReturn(Single.just(Arrays.asList(formOne)));
+
+        TestObserver<List<Form>> testObserver = formService.copyFromClient(DOMAIN, sourceUid, targetUid).test();
+        testObserver.assertNotComplete();
+        testObserver.assertError(FormAlreadyExistsException.class);
+    }
+}