keycloak-uncached

Merge pull request #1553 from mposolda/master KEYCLOAK-1295

8/21/2015 4:20:49 AM

Changes

integration/adapter-core/src/main/java/org/keycloak/adapters/ClientAuthAdapterUtils.java 50(+0 -50)

services/src/main/java/org/keycloak/authentication/authenticators/client/ValidateClientId.java 88(+0 -88)

services/src/main/java/org/keycloak/protocol/oidc/ServiceAccountManager.java 139(+0 -139)

Details

diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 8da15b6..423bf3e 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -27,26 +27,23 @@ import java.util.Map;
  */
 public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory {
 
-    // TODO Make configurable
+    // TODO Make it dynamic
     private String[] entities = new String[]{
             "org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
+            "org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
+            "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
+            "org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
             "org.keycloak.models.entities.IdentityProviderEntity",
             "org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
             "org.keycloak.models.entities.RequiredCredentialEntity",
             "org.keycloak.models.entities.CredentialEntity",
             "org.keycloak.models.entities.FederatedIdentityEntity",
-            "org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
-            "org.keycloak.models.sessions.mongo.entities.MongoUsernameLoginFailureEntity",
-            "org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity",
-            "org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity",
             "org.keycloak.models.entities.UserFederationProviderEntity",
             "org.keycloak.models.entities.UserFederationMapperEntity",
             "org.keycloak.models.entities.ProtocolMapperEntity",
             "org.keycloak.models.entities.IdentityProviderMapperEntity",
-            "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
-            "org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
             "org.keycloak.models.entities.AuthenticationExecutionEntity",
             "org.keycloak.models.entities.AuthenticationFlowEntity",
             "org.keycloak.models.entities.AuthenticatorConfigEntity",
diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
index 2a95905..3e32bbd 100755
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -16,7 +16,7 @@ public class AbstractOAuthClient {
     private final AtomicLong counter = new AtomicLong();
 
     protected String clientId;
-    protected Map<String, String> credentials;
+    protected Map<String, Object> credentials;
     protected String authUrl;
     protected String tokenUrl;
     protected RelativeUrlsUsed relativeUrlsUsed;
@@ -37,11 +37,11 @@ public class AbstractOAuthClient {
         this.clientId = clientId;
     }
 
-    public Map<String, String> getCredentials() {
+    public Map<String, Object> getCredentials() {
         return credentials;
     }
 
-    public void setCredentials(Map<String, String> credentials) {
+    public void setCredentials(Map<String, Object> credentials) {
         this.credentials = credentials;
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
index 608cbaa..a71dfe7 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
@@ -39,7 +39,7 @@ public class BaseAdapterConfig extends BaseRealmConfig {
     @JsonProperty("public-client")
     protected boolean publicClient;
     @JsonProperty("credentials")
-    protected Map<String, String> credentials = new HashMap<String, String>();
+    protected Map<String, Object> credentials = new HashMap<>();
 
 
     public boolean isUseResourceRoleMappings() {
@@ -114,11 +114,11 @@ public class BaseAdapterConfig extends BaseRealmConfig {
         this.enableBasicAuth = enableBasicAuth;
     }
 
-    public Map<String, String> getCredentials() {
+    public Map<String, Object> getCredentials() {
         return credentials;
     }
 
-    public void setCredentials(Map<String, String> credentials) {
+    public void setCredentials(Map<String, Object> credentials) {
         this.credentials = credentials;
     }
 
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSecretServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSecretServlet.java
new file mode 100644
index 0000000..43c70f8
--- /dev/null
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSecretServlet.java
@@ -0,0 +1,19 @@
+package org.keycloak.example;
+
+/**
+ * Client authentication with traditional OAuth2 client_id + client_secret
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ProductSAClientSecretServlet extends ProductServiceAccountServlet {
+
+    @Override
+    protected String getAdapterConfigLocation() {
+        return "WEB-INF/keycloak-client-secret.json";
+    }
+
+    @Override
+    protected String getClientAuthenticationMethod() {
+        return "secret";
+    }
+}
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSignedJWTServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSignedJWTServlet.java
new file mode 100644
index 0000000..2a6fe33
--- /dev/null
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSignedJWTServlet.java
@@ -0,0 +1,17 @@
+package org.keycloak.example;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {
+
+    @Override
+    protected String getAdapterConfigLocation() {
+        return "WEB-INF/keycloak-client-signed-jwt.json";
+    }
+
+    @Override
+    protected String getClientAuthenticationMethod() {
+        return "jwt";
+    }
+}
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
index d03654d..7ce0701 100644
--- a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
@@ -4,7 +4,9 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -26,30 +28,46 @@ import org.keycloak.VerificationException;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.ServerRequest;
-import org.keycloak.constants.ServiceAccountConstants;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.util.BasicAuthHelper;
 import org.keycloak.util.JsonSerialization;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class ProductServiceAccountServlet extends HttpServlet {
+public abstract class ProductServiceAccountServlet extends HttpServlet {
 
     public static final String ERROR = "error";
     public static final String TOKEN = "token";
     public static final String TOKEN_PARSED = "idTokenParsed";
     public static final String REFRESH_TOKEN = "refreshToken";
     public static final String PRODUCTS = "products";
+    public static final String CLIENT_AUTH_METHOD = "clientAuthMethod";
+
+    protected abstract String getAdapterConfigLocation();
+    protected abstract String getClientAuthenticationMethod();
+
+    public static String getLoginUrl(HttpServletRequest request) {
+        return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/login";
+    }
+
+    public static String getRefreshUrl(HttpServletRequest request) {
+        return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/refresh";
+    }
+
+    public static String getLogoutUrl(HttpServletRequest request) {
+        return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/logout";
+    }
 
     @Override
     public void init() throws ServletException {
-        InputStream config = getServletContext().getResourceAsStream("WEB-INF/keycloak.json");
+        String adapterConfigLocation = getAdapterConfigLocation();
+        InputStream config = getServletContext().getResourceAsStream(adapterConfigLocation);
         KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
-        HttpClient client = new DefaultHttpClient();
+        getServletContext().setAttribute("deployment-" + getClientAuthenticationMethod(), deployment);
 
-        getServletContext().setAttribute(KeycloakDeployment.class.getName(), deployment);
+        HttpClient client = new DefaultHttpClient();
         getServletContext().setAttribute(HttpClient.class.getName(), client);
     }
 
@@ -60,6 +78,8 @@ public class ProductServiceAccountServlet extends HttpServlet {
 
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        req.setAttribute(CLIENT_AUTH_METHOD, getClientAuthenticationMethod());
+
         String reqUri = req.getRequestURI();
         if (reqUri.endsWith("/login")) {
             serviceAccountLogin(req);
@@ -81,16 +101,21 @@ public class ProductServiceAccountServlet extends HttpServlet {
         KeycloakDeployment deployment = getKeycloakDeployment();
         HttpClient client = getHttpClient();
 
-        String clientId = deployment.getResourceName();
-        String clientSecret = deployment.getResourceCredentials().get("secret");
-
         try {
             HttpPost post = new HttpPost(deployment.getTokenUrl());
             List<NameValuePair> formparams = new ArrayList<NameValuePair>();
             formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
 
-            String authHeader = BasicAuthHelper.createHeader(clientId, clientSecret);
-            post.addHeader("Authorization", authHeader);
+            // Add client credentials according to the method configured in keycloak.json file
+            Map<String, String> reqHeaders = new HashMap<>();
+            Map<String, String> reqParams = new HashMap<>();
+            ClientCredentialsProviderUtils.setClientCredentials(deployment, reqHeaders, reqParams);
+            for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
+                post.setHeader(header.getKey(), header.getValue());
+            }
+            for (Map.Entry<String, String> param : reqParams.entrySet()) {
+                formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+            }
 
             UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
             post.setEntity(form);
@@ -217,7 +242,7 @@ public class ProductServiceAccountServlet extends HttpServlet {
     }
 
     private KeycloakDeployment getKeycloakDeployment() {
-        return (KeycloakDeployment) getServletContext().getAttribute(KeycloakDeployment.class.getName());
+        return (KeycloakDeployment) getServletContext().getAttribute("deployment-" + getClientAuthenticationMethod());
     }
 
     private HttpClient getHttpClient() {
diff --git a/examples/demo-template/service-account/src/main/resources/keystore-client.jks b/examples/demo-template/service-account/src/main/resources/keystore-client.jks
new file mode 100644
index 0000000..9b2a4d6
Binary files /dev/null and b/examples/demo-template/service-account/src/main/resources/keystore-client.jks differ
diff --git a/examples/demo-template/service-account/src/main/webapp/index.html b/examples/demo-template/service-account/src/main/webapp/index.html
index e2820d1..8c79bc6 100644
--- a/examples/demo-template/service-account/src/main/webapp/index.html
+++ b/examples/demo-template/service-account/src/main/webapp/index.html
@@ -1,5 +1,9 @@
 <html>
-    <head>
-        <meta http-equiv="Refresh" content="0; URL=app">
-    </head>
+    <head><title>Service account example</title></head>
+    <body>
+        <ul>
+            <li><a href="app-secret">Client authentication with client secret</a><br /><br /></li>
+            <li><a href="app-jwt">Client authentication with JWT signed by client private key</a></li>
+        </ul>
+    </body>
 </html>
\ No newline at end of file
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
new file mode 100644
index 0000000..6c07d1e
--- /dev/null
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
@@ -0,0 +1,17 @@
+{
+  "realm" : "demo",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8080/auth",
+  "ssl-required" : "external",
+  "resource" : "product-sa-client",
+  "credentials": {
+    "jwt": {
+      "client-keystore-file": "classpath:keystore-client.jks",
+      "client-keystore-type": "JKS",
+      "client-keystore-password": "storepass",
+      "client-key-password": "keypass",
+      "client-key-alias": "clientkey",
+      "token-expiration": 10
+    }
+  }
+}
\ No newline at end of file
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp b/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp
index e151f96..8296ddb 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp
@@ -13,10 +13,16 @@
     AccessToken token = (AccessToken) request.getSession().getAttribute(ProductServiceAccountServlet.TOKEN_PARSED);
     String products = (String) request.getAttribute(ProductServiceAccountServlet.PRODUCTS);
     String appError = (String) request.getAttribute(ProductServiceAccountServlet.ERROR);
+    String clientAuthMethod = (String) request.getAttribute(ProductServiceAccountServlet.CLIENT_AUTH_METHOD);
+
+    String loginUrl = ProductServiceAccountServlet.getLoginUrl(request);
+    String refreshUrl = ProductServiceAccountServlet.getRefreshUrl(request);
+    String logoutUrl = ProductServiceAccountServlet.getLogoutUrl(request);
 %>
 <h1>Service account portal</h1>
-<p><a href="/service-account-portal/app/login">Login</a> | <a href="/service-account-portal/app/refresh">Refresh token</a> | <a
-        href="/service-account-portal/app/logout">Logout</a></p>
+<h2>Client authentication method: <%= clientAuthMethod %></h2>
+<p><a href="<%= loginUrl %>">Login</a> | <a href="<%= refreshUrl %>">Refresh token</a> | <a
+        href="<%= logoutUrl %>">Logout</a></p>
 <hr />
 
 <% if (appError != null) { %>
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml b/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml
index 5dc7103..024a522 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml
@@ -7,13 +7,23 @@
     <module-name>service-account-portal</module-name>
 
     <servlet>
-        <servlet-name>ServiceAccountExample</servlet-name>
-        <servlet-class>org.keycloak.example.ProductServiceAccountServlet</servlet-class>
+        <servlet-name>ProductSAClientSecretServlet</servlet-name>
+        <servlet-class>org.keycloak.example.ProductSAClientSecretServlet</servlet-class>
     </servlet>
 
+    <servlet>
+        <servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
+        <servlet-class>org.keycloak.example.ProductSAClientSignedJWTServlet</servlet-class>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>ProductSAClientSecretServlet</servlet-name>
+        <url-pattern>/app-secret/*</url-pattern>
+    </servlet-mapping>
+
     <servlet-mapping>
-        <servlet-name>ServiceAccountExample</servlet-name>
-        <url-pattern>/app/*</url-pattern>
+        <servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
+        <url-pattern>/app-jwt/*</url-pattern>
     </servlet-mapping>
 
 </web-app>
\ No newline at end of file
diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index 556708e..8d3e597 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -173,7 +173,10 @@
             "clientId": "product-sa-client",
             "enabled": true,
             "secret": "password",
-            "serviceAccountsEnabled": true
+            "serviceAccountsEnabled": true,
+            "attributes": {
+                "jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
+            }
         }
     ],
     "clientScopeMappings": {
diff --git a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java
index a3972b3..cb6ac99 100755
--- a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java
+++ b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java
@@ -27,8 +27,6 @@ public class Bootstrap implements ServletContextListener {
         ServletContext context = sce.getServletContext();
 
         configureClient(context);
-
-        client.start();
         context.setAttribute(ServletOAuthClient.class.getName(), client);
     }
 
diff --git a/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java b/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java
index bd6a395..1fe4a2f 100755
--- a/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java
+++ b/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java
@@ -41,7 +41,6 @@ public class AppContextListener implements ServletContextListener {
         }
         ServletOAuthClientBuilder.build(is, oauthClient);
         logger.info("OAuth client configured and started");
-        oauthClient.start();
     }
 
     @Override
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index bc60afe..0043d9a 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -52,6 +52,11 @@
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
index f1cd341..a7cf098 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
@@ -1,5 +1,6 @@
 package org.keycloak.federation.ldap.idm.model;
 
+import java.util.Collection;
 import java.util.Deque;
 import java.util.LinkedList;
 import java.util.regex.Matcher;
@@ -26,6 +27,10 @@ public class LDAPDn {
 
     @Override
     public String toString() {
+        return toString(entries);
+    }
+
+    private static String toString(Collection<Entry> entries) {
         StringBuilder builder = new StringBuilder();
 
         boolean first = true;
@@ -62,7 +67,9 @@ public class LDAPDn {
      * @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
      */
     public String getParentDn() {
-        return new LinkedList<>(entries).remove().toString();
+        LinkedList<Entry> parentDnEntries = new LinkedList<>(entries);
+        parentDnEntries.remove();
+        return toString(parentDnEntries);
     }
 
     public void addFirst(String rdnName, String rdnValue) {
diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
new file mode 100644
index 0000000..77bc4ce
--- /dev/null
+++ b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
@@ -0,0 +1,22 @@
+package org.keycloak.federation.ldap.idm.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPDnTest {
+
+    @Test
+    public void testDn() throws Exception {
+        LDAPDn dn = LDAPDn.fromString("dc=keycloak, dc=org");
+        dn.addFirst("ou", "People");
+        Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.toString());
+
+        dn.addFirst("uid", "Johny,Depp");
+        Assert.assertEquals("uid=Johny\\,Depp,ou=People,dc=keycloak,dc=org", dn.toString());
+
+        Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn());
+    }
+}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index d0fc402..dfbd4da 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -654,7 +654,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ClientSecretCtrl'
         })
-        .when('/realms/:realm/clients/:client/credentials/client-signed-jwt', {
+        .when('/realms/:realm/clients/:client/credentials/client-jwt', {
             templateUrl : resourceUrl + '/partials/client-credentials-jwt.html',
             resolve : {
                 realm : function(RealmLoader) {
@@ -666,7 +666,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ClientSignedJWTCtrl'
         })
-        .when('/realms/:realm/clients/:client/credentials/client-signed-jwt/:keyType/import/:attribute', {
+        .when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/import/:attribute', {
             templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-import.html',
             resolve : {
                 realm : function(RealmLoader) {
@@ -681,7 +681,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ClientCertificateImportCtrl'
         })
-        .when('/realms/:realm/clients/:client/credentials/client-signed-jwt/:keyType/export/:attribute', {
+        .when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/export/:attribute', {
             templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-export.html',
             resolve : {
                 realm : function(RealmLoader) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index b50c4a0..c6924c9 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -81,11 +81,11 @@ module.controller('ClientSignedJWTCtrl', function($scope, $location, realm, clie
     );
 
     $scope.importCertificate = function() {
-        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt/Signing/import/jwt.credentials");
+        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/import/jwt.credentials");
     };
 
     $scope.generateSigningKey = function() {
-        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt/Signing/export/jwt.credentials");
+        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/export/jwt.credentials");
     };
 
     $scope.cancel = function() {
@@ -263,7 +263,7 @@ module.controller('ClientCertificateImportCtrl', function($scope, $location, $ht
         var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/saml/keys";
     } else if (callingContext == 'jwt-credentials') {
         var uploadUrl = authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/certificates/' + attribute + '/upload-certificate';
-        var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt";
+        var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt";
     }
 
     $scope.files = [];
@@ -371,6 +371,12 @@ module.controller('ClientCertificateExportCtrl', function($scope, $location, $ht
             });
             var ext = ".jks";
             if ($scope.jks.format == 'PKCS12') ext = ".p12";
+
+            if (callingContext == 'jwt-credentials') {
+                $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt");
+                Notifications.success("New keypair and certificate generated successfully. Download keystore file")
+            }
+
             saveAs(blob, 'keystore' + ext);
         }).error(function(data) {
             var errorMsg = 'Error downloading';
@@ -390,7 +396,7 @@ module.controller('ClientCertificateExportCtrl', function($scope, $location, $ht
     });
 
     $scope.cancel = function() {
-        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt");
+        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt");
     }
 });
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 00da83c..a33b84f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1622,7 +1622,15 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
 });
 
 module.controller('RealmFlowBindingCtrl', function($scope, flows, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
-    $scope.flows = flows;
+    $scope.flows = [];
+    $scope.clientFlows = [];
+    for (var i=0 ; i<flows.length ; i++) {
+        if (flows[i].providerId == 'client-flow') {
+            $scope.clientFlows.push(flows[i]);
+        } else {
+            $scope.flows.push(flows[i]);
+        }
+    }
 
     genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/flow-bindings");
 });
@@ -1792,7 +1800,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
     };
 
     $scope.addFlow = function() {
-        $location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/flow/execution');
+        $location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/flow/execution/' + $scope.flow.id);
 
     }
 
@@ -1807,7 +1815,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
     }
 
     $scope.addExecution = function() {
-        $location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/execution');
+        $location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/execution/' + $scope.flow.id);
 
     }
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html
index e645c2e..7a9cfb0 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html
@@ -46,6 +46,17 @@
             <kc-tooltip>Select the flow you want to use when the user has forgotten their credentials.</kc-tooltip>
         </div>
 
+        <div class="form-group">
+            <label for="resetCredentials" class="col-md-2 control-label">Client Authentication</label>
+            <div class="col-md-2">
+                <div>
+                    <select id="clientAuthentication" ng-model="realm.clientAuthenticationFlow" class="form-control" ng-options="flow.alias as flow.alias for flow in clientFlows">
+                    </select>
+                </div>
+            </div>
+            <kc-tooltip>Select the flow you want to use for authentication of clients.</kc-tooltip>
+        </div>
+
         <div class="form-group" data-ng-show="access.manageRealm">
             <div class="col-md-12">
                 <button kc-save data-ng-disabled="!changed">Save</button>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
index 5f580ab..5290b18 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
@@ -9,7 +9,7 @@
 
     <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageClients">
         <fieldset class="form-group col-sm-10">
-            <legend uncollapsed><span class="text">Client Certificate</span>  <kc-tooltip>Client Certificate for validate JWT issued by client and signed by Client private key.</kc-tooltip></legend>
+            <legend uncollapsed><span class="text">Client Certificate</span>  <kc-tooltip>Client Certificate for validate JWT issued by client and signed by Client private key from your keystore.</kc-tooltip></legend>
             <div class="form-group" data-ng-hide="!signingKeyInfo.certificate">
                 <label class="col-md-2 control-label" for="signingCert">Certificate</label>
 
@@ -19,11 +19,11 @@
                 </div>
             </div>
             <div class="form-group" data-ng-show="!signingKeyInfo.certificate">
-                <label class="col-md-2 control-label" for="signingCert">Client Certificate not yet generated or imported!</label>
+                <label class="col-md-4 control-label" for="signingCert">Client Certificate not yet generated or imported!</label>
             </div>
             <div class="form-group">
                 <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
-                    <button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">Generate new keys</button>
+                    <button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">Generate new keys and certificate</button>
                     <button class="btn btn-default" type="submit" data-ng-click="importCertificate()">Import certificate</button>
                     <button class="btn btn-default" type="buttin" data-ng-click="cancel()">Cancel</button>
                 </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html
index b933e9a..b00a227 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html
@@ -3,7 +3,7 @@
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
         <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
-        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-signed-jwt">Signed JWT config</a></li>
+        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-jwt">Signed JWT config</a></li>
         <li class="active">Generate Client Private Key</li>
     </ol>
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html
index 82b255d..f11a1ea 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html
@@ -3,7 +3,7 @@
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
         <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
-        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-signed-jwt">Signed JWT config</a></li>
+        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-jwt">Signed JWT config</a></li>
         <li class="active">Client Certificate Import</li>
     </ol>
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow.html
index 564261a..ccc9fd7 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow.html
@@ -29,7 +29,7 @@
                     </select>
                 </div>
             </div>
-            <kc-tooltip>What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for everything else</kc-tooltip>
+            <kc-tooltip>What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for users and everything else</kc-tooltip>
         </div>
         <div class="form-group">
             <div class="col-md-10 col-md-offset-2">
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index b9b495b..76aaec3 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -5,6 +5,7 @@ import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
 import org.jboss.logging.Logger;
+import org.keycloak.adapters.authentication.ClientCredentialsProvider;
 import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.enums.TokenStore;
@@ -253,16 +254,26 @@ public class AdapterDeploymentContext {
         }
 
         @Override
-        public Map<String, String> getResourceCredentials() {
+        public Map<String, Object> getResourceCredentials() {
             return delegate.getResourceCredentials();
         }
 
         @Override
-        public void setResourceCredentials(Map<String, String> resourceCredentials) {
+        public void setResourceCredentials(Map<String, Object> resourceCredentials) {
             delegate.setResourceCredentials(resourceCredentials);
         }
 
         @Override
+        public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
+            delegate.setClientAuthenticator(clientAuthenticator);
+        }
+
+        @Override
+        public ClientCredentialsProvider getClientAuthenticator() {
+            return delegate.getClientAuthenticator();
+        }
+
+        @Override
         public HttpClient getClient() {
             return delegate.getClient();
         }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java
new file mode 100644
index 0000000..b7e7a28
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java
@@ -0,0 +1,19 @@
+package org.keycloak.adapters.authentication;
+
+import java.util.Map;
+
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * TODO: Javadoc
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface ClientCredentialsProvider {
+
+    String getId();
+
+    void init(KeycloakDeployment deployment, Object config);
+
+    void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams);
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java
new file mode 100644
index 0000000..df8b880
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java
@@ -0,0 +1,85 @@
+package org.keycloak.adapters.authentication;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.message.BasicNameValuePair;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ClientCredentialsProviderUtils {
+
+    private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
+
+    public static ClientCredentialsProvider bootstrapClientAuthenticator(KeycloakDeployment deployment) {
+        String clientId = deployment.getResourceName();
+        Map<String, Object> clientCredentials = deployment.getResourceCredentials();
+
+        String authenticatorId;
+        if (clientCredentials == null || clientCredentials.isEmpty()) {
+            authenticatorId = ClientIdAndSecretCredentialsProvider.PROVIDER_ID;
+        } else {
+            authenticatorId = (String) clientCredentials.get("provider");
+            if (authenticatorId == null) {
+                // If there is just one credential type, use provider from it
+                if (clientCredentials.size() == 1) {
+                    authenticatorId = clientCredentials.keySet().iterator().next();
+                } else {
+                    throw new RuntimeException("Can't identify clientAuthenticator from the configuration of client '" + clientId + "' . Check your adapter configurations");
+                }
+            }
+        }
+
+        logger.debugf("Using provider '%s' for authentication of client '%s'", authenticatorId, clientId);
+
+        Map<String, ClientCredentialsProvider> authenticators = new HashMap<>();
+        loadAuthenticators(authenticators, ClientCredentialsProviderUtils.class.getClassLoader());
+        loadAuthenticators(authenticators, Thread.currentThread().getContextClassLoader());
+
+        ClientCredentialsProvider authenticator = authenticators.get(authenticatorId);
+        if (authenticator == null) {
+            throw new RuntimeException("Couldn't find ClientCredentialsProvider implementation class with id: " + authenticatorId + ". Loaded authentication providers: " + authenticators.keySet());
+        }
+
+        Object config = (clientCredentials==null) ? null : clientCredentials.get(authenticatorId);
+        authenticator.init(deployment, config);
+
+        return authenticator;
+    }
+
+    private static void loadAuthenticators(Map<String, ClientCredentialsProvider> authenticators, ClassLoader classLoader) {
+        for (ClientCredentialsProvider authenticator : ServiceLoader.load(ClientCredentialsProvider.class, classLoader)) {
+            authenticators.put(authenticator.getId(), authenticator);
+        }
+    }
+
+    public static void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formparams) {
+        ClientCredentialsProvider authenticator = deployment.getClientAuthenticator();
+        authenticator.setClientCredentials(deployment, requestHeaders, formparams);
+    }
+
+    /**
+     * Don't use directly from your JEE apps to avoid HttpClient linkage errors! Instead use the method {@link #setClientCredentials(KeycloakDeployment, Map, Map)}
+     */
+    public static void setClientCredentials(KeycloakDeployment deployment, HttpPost post, List<NameValuePair> formparams) {
+        Map<String, String> reqHeaders = new HashMap<>();
+        Map<String, String> reqParams = new HashMap<>();
+        setClientCredentials(deployment, reqHeaders, reqParams);
+
+        for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
+            post.setHeader(header.getKey(), header.getValue());
+        }
+
+        for (Map.Entry<String, String> param : reqParams.entrySet()) {
+            formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+        }
+    }
+
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientIdAndSecretCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientIdAndSecretCredentialsProvider.java
new file mode 100644
index 0000000..ef39ff9
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientIdAndSecretCredentialsProvider.java
@@ -0,0 +1,49 @@
+package org.keycloak.adapters.authentication;
+
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.util.BasicAuthHelper;
+
+/**
+ * Traditional OAuth2 authentication of clients based on client_id and client_secret
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ClientIdAndSecretCredentialsProvider implements ClientCredentialsProvider {
+
+    private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
+
+    public static final String PROVIDER_ID = CredentialRepresentation.SECRET;
+
+    private String clientSecret;
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public void init(KeycloakDeployment deployment, Object config) {
+        clientSecret = (String) config;
+    }
+
+    @Override
+    public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
+        String clientId = deployment.getResourceName();
+
+        if (!deployment.isPublicClient()) {
+            if (clientSecret != null) {
+                String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
+                requestHeaders.put("Authorization", authorization);
+            } else {
+                logger.warnf("Client '%s' doesn't have secret available", clientId);
+            }
+        } else {
+            formParams.put(OAuth2Constants.CLIENT_ID, clientId);
+        }
+    }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
new file mode 100644
index 0000000..e249a43
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
@@ -0,0 +1,103 @@
+package org.keycloak.adapters.authentication;
+
+import java.security.PrivateKey;
+import java.util.Map;
+
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.util.KeystoreUtil;
+import org.keycloak.util.Time;
+
+/**
+ * Client authentication based on JWT signed by client private key
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
+
+    public static final String PROVIDER_ID = "jwt";
+
+    private PrivateKey privateKey;
+    private int tokenTimeout;
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    public void setPrivateKey(PrivateKey privateKey) {
+        this.privateKey = privateKey;
+    }
+
+    public void setTokenTimeout(int tokenTimeout) {
+        this.tokenTimeout = tokenTimeout;
+    }
+
+    @Override
+    public void init(KeycloakDeployment deployment, Object config) {
+        if (config == null || !(config instanceof Map)) {
+            throw new RuntimeException("Configuration of jwt credentials is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
+        }
+
+        Map<String, Object> cfg = (Map<String, Object>) config;
+
+        String clientKeystoreFile =  (String) cfg.get("client-keystore-file");
+        if (clientKeystoreFile == null) {
+            throw new RuntimeException("Missing parameter client-keystore-file in configuration of jwt for client " + deployment.getResourceName());
+        }
+
+        String clientKeystoreType = (String) cfg.get("client-keystore-type");
+        KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType);
+
+        String clientKeystorePassword =  (String) cfg.get("client-keystore-password");
+        if (clientKeystorePassword == null) {
+            throw new RuntimeException("Missing parameter client-keystore-password in configuration of jwt for client " + deployment.getResourceName());
+        }
+
+        String clientKeyPassword = (String) cfg.get("client-key-password");
+        if (clientKeyPassword == null) {
+            clientKeyPassword = clientKeystorePassword;
+        }
+
+        String clientKeyAlias =  (String) cfg.get("client-key-alias");
+        if (clientKeyAlias == null) {
+            clientKeyAlias = deployment.getResourceName();
+        }
+        this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
+
+        Integer tokenExp = (Integer) cfg.get("token-timeout");
+        this.tokenTimeout = (tokenExp==null) ? 10 : tokenExp;
+    }
+
+    @Override
+    public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
+        String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
+
+        formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
+        formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
+    }
+
+    public String createSignedRequestToken(String clientId, String realmInfoUrl) {
+        JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
+        return new JWSBuilder()
+                .jsonContent(jwt)
+                .rsa256(privateKey);
+    }
+
+    protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
+        JsonWebToken reqToken = new JsonWebToken();
+        reqToken.id(AdapterUtils.generateId());
+        reqToken.issuer(clientId);
+        reqToken.audience(realmInfoUrl);
+
+        int now = Time.currentTime();
+        reqToken.issuedAt(now);
+        reqToken.expiration(now + this.tokenTimeout);
+        reqToken.notBefore(now);
+
+        return reqToken;
+    }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
index ae3888c..5ce1b6e 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
@@ -9,6 +9,7 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.message.BasicNameValuePair;
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
 import org.keycloak.constants.ServiceUrlConstants;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.util.BasicAuthHelper;
@@ -76,13 +77,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
     	    formparams.add(new BasicNameValuePair("username", username));
     	    formparams.add(new BasicNameValuePair("password", password));
 
-    	    if (deployment.isPublicClient()) {
-    	        formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
-    	    } else {
-    	        String authorization = BasicAuthHelper.createHeader(deployment.getResourceName(),
-    	                deployment.getResourceCredentials().get("secret"));
-    	        post.setHeader("Authorization", authorization);
-    	    }
+			ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
     	    UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
     	    post.setEntity(form);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java
index a11e1ee..6ae1ed6 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java
@@ -23,6 +23,7 @@ import org.apache.http.message.BasicNameValuePair;
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.VerificationException;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
 import org.keycloak.constants.ServiceUrlConstants;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.util.BasicAuthHelper;
@@ -72,14 +73,8 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
         formparams.add(new BasicNameValuePair("username", username));
         formparams.add(new BasicNameValuePair("password", password));
 
-        if (deployment.isPublicClient()) { // if client is public access type
-            formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
-        } else {
-            String clientId = deployment.getResourceName();
-            String clientSecret = deployment.getResourceCredentials().get("secret");
-            String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
-            post.setHeader("Authorization", authorization);
-        }
+        ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
+
         UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
 
@@ -135,15 +130,7 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
                 HttpPost post = new HttpPost(logoutUri);
 
                 List<NameValuePair> formparams = new ArrayList<NameValuePair>();
-                if (deployment.isPublicClient()) { // if client is public access type
-                    formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
-                } else {
-                    String clientId = deployment.getResourceName();
-                    String clientSecret = deployment.getResourceCredentials().get("secret");
-                    String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
-                    post.setHeader("Authorization", authorization);
-                }
-
+                ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
                 formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
 
                 UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 01960a4..f927382 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -2,6 +2,7 @@ package org.keycloak.adapters;
 
 import org.apache.http.client.HttpClient;
 import org.jboss.logging.Logger;
+import org.keycloak.adapters.authentication.ClientCredentialsProvider;
 import org.keycloak.constants.ServiceUrlConstants;
 import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.enums.SslRequired;
@@ -39,7 +40,8 @@ public class KeycloakDeployment {
     protected boolean bearerOnly;
     protected boolean enableBasicAuth;
     protected boolean publicClient;
-    protected Map<String, String> resourceCredentials = new HashMap<String, String>();
+    protected Map<String, Object> resourceCredentials = new HashMap<>();
+    protected ClientCredentialsProvider clientAuthenticator;
     protected HttpClient client;
 
     protected String scope;
@@ -216,14 +218,22 @@ public class KeycloakDeployment {
         this.publicClient = publicClient;
     }
 
-    public Map<String, String> getResourceCredentials() {
+    public Map<String, Object> getResourceCredentials() {
         return resourceCredentials;
     }
 
-    public void setResourceCredentials(Map<String, String> resourceCredentials) {
+    public void setResourceCredentials(Map<String, Object> resourceCredentials) {
         this.resourceCredentials = resourceCredentials;
     }
 
+    public ClientCredentialsProvider getClientAuthenticator() {
+        return clientAuthenticator;
+    }
+
+    public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
+        this.clientAuthenticator = clientAuthenticator;
+    }
+
     public HttpClient getClient() {
         return client;
     }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 5ddc54b..c430258 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -3,6 +3,7 @@ package org.keycloak.adapters;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 import org.jboss.logging.Logger;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.enums.TokenStore;
 import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -55,7 +56,10 @@ public class KeycloakDeploymentBuilder {
             deployment.setTokenStore(TokenStore.SESSION);
         }
         if (adapterConfig.getPrincipalAttribute() != null) deployment.setPrincipalAttribute(adapterConfig.getPrincipalAttribute());
+
         deployment.setResourceCredentials(adapterConfig.getCredentials());
+        deployment.setClientAuthenticator(ClientCredentialsProviderUtils.bootstrapClientAuthenticator(deployment));
+
         deployment.setPublicClient(adapterConfig.isPublicClient());
         deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());
 
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index 0bf2cf4..bbfd869 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -8,10 +8,9 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.message.BasicNameValuePair;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.util.BasicAuthHelper;
 import org.keycloak.util.HostUtils;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.KeycloakUriBuilder;
@@ -23,7 +22,6 @@ import java.io.InputStream;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -50,30 +48,17 @@ public class ServerRequest {
     }
 
     public static void invokeLogout(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
-        String client_id = deployment.getResourceName();
-        Map<String, String> credentials = deployment.getResourceCredentials();
         HttpClient client = deployment.getClient();
         URI uri = deployment.getLogoutUrl().clone().build();
-        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
-        for (Map.Entry<String, String> entry : credentials.entrySet()) {
-            formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
-        }
+        List<NameValuePair> formparams = new ArrayList<>();
+
         formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
-        HttpResponse response = null;
         HttpPost post = new HttpPost(uri);
-        if (!deployment.isPublicClient()) {
-            String clientSecret = credentials.get(CredentialRepresentation.SECRET);
-            if (clientSecret != null) {
-                String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
-                post.setHeader("Authorization", authorization);
-            }
-        } else {
-            formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
-        }
+        ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
         UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
-        response = client.execute(post);
+        HttpResponse response = client.execute(post);
         int status = response.getStatusLine().getStatusCode();
         HttpEntity entity = response.getEntity();
         if (status != 204) {
@@ -86,17 +71,8 @@ public class ServerRequest {
         if (is != null) is.close();
     }
 
-    public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws HttpFailure, IOException {
-        String tokenUrl = deployment.getTokenUrl();
-        String client_id = deployment.getResourceName();
-        Map<String, String> credentials = deployment.getResourceCredentials();
-        HttpClient client = deployment.getClient();
-
-        return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, tokenUrl, redirectUri, client_id, credentials, sessionId);
-    }
-
-    public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String tokenUrl, String redirectUri, String client_id, Map<String, String> credentials, String sessionId) throws IOException, HttpFailure {
-        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+    public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws IOException, HttpFailure {
+        List<NameValuePair> formparams = new ArrayList<>();
         redirectUri = stripOauthParametersFromRedirect(redirectUri);
         formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
         formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
@@ -105,21 +81,13 @@ public class ServerRequest {
             formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
             formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
         }
-        HttpResponse response = null;
-        HttpPost post = new HttpPost(tokenUrl);
-        if (!publicClient) {
-            String clientSecret = credentials.get(CredentialRepresentation.SECRET);
-            if (clientSecret != null) {
-                String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
-                post.setHeader("Authorization", authorization);
-            }
-        } else {
-            formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
-        }
+
+        HttpPost post = new HttpPost(deployment.getTokenUrl());
+        ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
         UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
-        response = client.execute(post);
+        HttpResponse response = deployment.getClient().execute(post);
         int status = response.getStatusLine().getStatusCode();
         HttpEntity entity = response.getEntity();
         if (status != 200) {
@@ -152,36 +120,16 @@ public class ServerRequest {
     }
 
     public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
-        String tokenUrl = deployment.getTokenUrl();
-        String client_id = deployment.getResourceName();
-        Map<String, String> credentials = deployment.getResourceCredentials();
-        HttpClient client = deployment.getClient();
-        return invokeRefresh(client, deployment.isPublicClient(), refreshToken, tokenUrl, client_id, credentials);
-    }
-
-
-    public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String tokenUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
         List<NameValuePair> formparams = new ArrayList<NameValuePair>();
-        for (Map.Entry<String, String> entry : credentials.entrySet()) {
-            formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
-        }
         formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
         formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
-        HttpResponse response = null;
-        HttpPost post = new HttpPost(tokenUrl);
-        if (!publicClient) {
-            String clientSecret = credentials.get(CredentialRepresentation.SECRET);
-            if (clientSecret != null) {
-                String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
-                post.setHeader("Authorization", authorization);
-            }
-        } else {
-            formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
-        }
+
+        HttpPost post = new HttpPost(deployment.getTokenUrl());
+        ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
         UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
-        response = client.execute(post);
+        HttpResponse response = deployment.getClient().execute(post);
         int status = response.getStatusLine().getStatusCode();
         HttpEntity entity = response.getEntity();
         if (status != 200) {
@@ -215,43 +163,28 @@ public class ServerRequest {
 
     public static void invokeRegisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
         String registerNodeUrl = deployment.getRegisterNodeUrl();
-        String client_id = deployment.getResourceName();
-        Map<String, String> credentials = deployment.getResourceCredentials();
-        HttpClient client = deployment.getClient();
-
-        invokeClientManagementRequest(client, host, registerNodeUrl, client_id, credentials);
+        invokeClientManagementRequest(deployment, host, registerNodeUrl);
     }
 
     public static void invokeUnregisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
         String unregisterNodeUrl = deployment.getUnregisterNodeUrl();
-        String client_id = deployment.getResourceName();
-        Map<String, String> credentials = deployment.getResourceCredentials();
-        HttpClient client = deployment.getClient();
-
-        invokeClientManagementRequest(client, host, unregisterNodeUrl, client_id, credentials);
+        invokeClientManagementRequest(deployment, host, unregisterNodeUrl);
     }
 
-    public static void invokeClientManagementRequest(HttpClient client, String host, String endpointUrl, String clientId, Map<String, String> credentials) throws HttpFailure, IOException {
+    public static void invokeClientManagementRequest(KeycloakDeployment deployment, String host, String endpointUrl) throws HttpFailure, IOException {
         if (endpointUrl == null) {
-            throw new IOException("You need to configure URI for register/unregister node for application " + clientId);
+            throw new IOException("You need to configure URI for register/unregister node for application " + deployment.getResourceName());
         }
 
         List<NameValuePair> formparams = new ArrayList<NameValuePair>();
         formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_CLUSTER_HOST, host));
 
         HttpPost post = new HttpPost(endpointUrl);
-
-        String clientSecret = credentials.get(CredentialRepresentation.SECRET);
-        if (clientSecret != null) {
-            String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
-            post.setHeader("Authorization", authorization);
-        }  else {
-            throw new IOException("You need to configure clientSecret for register/unregister node for application " + clientId);
-        }
+        ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
         UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
-        HttpResponse response = client.execute(post);
+        HttpResponse response = deployment.getClient().execute(post);
         int status = response.getStatusLine().getStatusCode();
         if (status != 204) {
             HttpEntity entity = response.getEntity();
diff --git a/integration/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider b/integration/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider
new file mode 100644
index 0000000..3b2124b
--- /dev/null
+++ b/integration/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider
@@ -0,0 +1,2 @@
+org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
+org.keycloak.adapters.authentication.JWTClientCredentialsProvider
\ No newline at end of file
diff --git a/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index c326d76..a0a24b2 100644
--- a/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -2,6 +2,9 @@ package org.keycloak.adapters;
 
 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
 import org.junit.Test;
+import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
+import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
+import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.enums.TokenStore;
 import org.keycloak.util.PemUtils;
@@ -32,8 +35,10 @@ public class KeycloakDeploymentBuilderTest {
         assertTrue(deployment.isEnableBasicAuth());
         assertTrue(deployment.isExposeToken());
         assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
+        assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
         assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
         assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
+        assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
         assertTrue(deployment.isAlwaysRefreshToken());
         assertTrue(deployment.isRegisterNodeAtStartup());
         assertEquals(1000, deployment.getRegisterNodePeriod());
@@ -41,4 +46,16 @@ public class KeycloakDeploymentBuilderTest {
         assertEquals("email", deployment.getPrincipalAttribute());
     }
 
+    @Test
+    public void loadNoClientCredentials() throws Exception {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
+        assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
+    }
+
+    @Test
+    public void loadJwtCredentials() throws Exception {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-jwt.json"));
+        assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
+    }
+
 }
diff --git a/integration/adapter-core/src/test/resources/keycloak.json b/integration/adapter-core/src/test/resources/keycloak.json
index 32a8df3..afa00f5 100644
--- a/integration/adapter-core/src/test/resources/keycloak.json
+++ b/integration/adapter-core/src/test/resources/keycloak.json
@@ -22,8 +22,8 @@
     "truststore": "classpath:/cacerts.jks",
     "truststore-password": "changeit",
     "client-keystore": "classpath:/keystore.jks",
-    "client-keystore-password": "changeit",
-    "client-key-password": "password",
+    "client-keystore-password": "storepass",
+    "client-key-password": "keypass",
     "auth-server-url-for-backend-requests": "https://backend:8443/auth",
     "always-refresh-token": true,
     "register-node-at-startup": true,
diff --git a/integration/adapter-core/src/test/resources/keycloak-jwt.json b/integration/adapter-core/src/test/resources/keycloak-jwt.json
new file mode 100644
index 0000000..6e46f33
--- /dev/null
+++ b/integration/adapter-core/src/test/resources/keycloak-jwt.json
@@ -0,0 +1,13 @@
+{
+    "realm": "demo",
+    "resource": "customer-portal",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "https://localhost:8443/auth",
+    "ssl-required": "external",
+    "credentials": {
+        "jwt": {
+            "client-keystore-file": "classpath:keystore.jks",
+            "client-keystore-password": "storepass"
+        }
+  }
+}
\ No newline at end of file
diff --git a/integration/adapter-core/src/test/resources/keycloak-no-credentials.json b/integration/adapter-core/src/test/resources/keycloak-no-credentials.json
new file mode 100644
index 0000000..5f223ac
--- /dev/null
+++ b/integration/adapter-core/src/test/resources/keycloak-no-credentials.json
@@ -0,0 +1,8 @@
+{
+    "realm": "demo",
+    "resource": "customer-portal",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "https://localhost:8443/auth",
+    "public-client": true,
+    "expose-token": true
+}
\ No newline at end of file
diff --git a/integration/adapter-core/src/test/resources/keystore.jks b/integration/adapter-core/src/test/resources/keystore.jks
index 0c4e3a1..1d62fb2 100644
Binary files a/integration/adapter-core/src/test/resources/keystore.jks and b/integration/adapter-core/src/test/resources/keystore.jks differ
diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
index 684f268..7bbea5a 100755
--- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
+++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
@@ -50,8 +50,8 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
                 .param(OAuth2Constants.CODE, code)
                 .param(OAuth2Constants.CLIENT_ID, clientId)
                 .param(OAuth2Constants.REDIRECT_URI, redirectUri);
-        for (Map.Entry<String, String> entry : credentials.entrySet()) {
-            codeForm.param(entry.getKey(), entry.getValue());
+        for (Map.Entry<String, Object> entry : credentials.entrySet()) {
+            codeForm.param(entry.getKey(), (String) entry.getValue());
         }
         Response res = client.target(tokenUrl).request().post(Entity.form(codeForm));
         try {
diff --git a/integration/servlet-oauth-client/pom.xml b/integration/servlet-oauth-client/pom.xml
index 7d957ca..b5c869f 100755
--- a/integration/servlet-oauth-client/pom.xml
+++ b/integration/servlet-oauth-client/pom.xml
@@ -45,6 +45,27 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-xc</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>${jboss.logging.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java
new file mode 100644
index 0000000..d24b0b4
--- /dev/null
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java
@@ -0,0 +1,83 @@
+package org.keycloak.servlet;
+
+import java.util.Map;
+
+import org.keycloak.AbstractOAuthClient;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.enums.RelativeUrlsUsed;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KeycloakDeploymentDelegateOAuthClient extends AbstractOAuthClient {
+
+    private KeycloakDeployment deployment;
+
+    public KeycloakDeployment getDeployment() {
+        return deployment;
+    }
+
+    public void setDeployment(KeycloakDeployment deployment) {
+        this.deployment = deployment;
+    }
+
+    @Override
+    public String getClientId() {
+        return deployment.getResourceName();
+    }
+
+    @Override
+    public void setClientId(String clientId) {
+        deployment.setResourceName(clientId);
+    }
+
+    @Override
+    public Map<String, Object> getCredentials() {
+        return deployment.getResourceCredentials();
+    }
+
+    @Override
+    public void setCredentials(Map<String, Object> credentials) {
+        deployment.setResourceCredentials(credentials);
+    }
+
+    @Override
+    public String getAuthUrl() {
+        return deployment.getAuthUrl().clone().build().toString();
+    }
+
+    @Override
+    public void setAuthUrl(String authUrl) {
+        throw new IllegalStateException("Illegal to call this method");
+    }
+
+    @Override
+    public String getTokenUrl() {
+        return deployment.getTokenUrl();
+    }
+
+    @Override
+    public void setTokenUrl(String tokenUrl) {
+        throw new IllegalStateException("Illegal to call this method");
+    }
+
+    @Override
+    public boolean isPublicClient() {
+        return deployment.isPublicClient();
+    }
+
+    @Override
+    public void setPublicClient(boolean publicClient) {
+        deployment.setPublicClient(publicClient);
+    }
+
+    @Override
+    public RelativeUrlsUsed getRelativeUrlsUsed() {
+        return deployment.getRelativeUrls();
+    }
+
+    @Override
+    public void setRelativeUrlsUsed(RelativeUrlsUsed relativeUrlsUsed) {
+        throw new IllegalStateException("Illegal to call this method");
+    }
+}
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 5f5daaa..3d61013 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -1,14 +1,10 @@
 package org.keycloak.servlet;
 
-import org.apache.http.client.HttpClient;
-import org.keycloak.AbstractOAuthClient;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.adapters.HttpClientBuilder;
 import org.keycloak.adapters.ServerRequest;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
-import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.KeycloakUriBuilder;
 import org.keycloak.util.UriUtils;
 
@@ -22,24 +18,18 @@ import java.net.URI;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class ServletOAuthClient extends AbstractOAuthClient {
-    protected HttpClient client;
-    protected AdapterConfig adapterConfig;
-
-    public void start() {
-        client = new HttpClientBuilder().build(adapterConfig);
-    }
+public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
 
     /**
      * closes client
      */
     public void stop() {
-        client.getConnectionManager().shutdown();
+        getDeployment().getClient().getConnectionManager().shutdown();
     }
 
     private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
         // Don't send sessionId in oauth clients for now
-        return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, tokenUrl, false), redirectUri, clientId, credentials, null);
+        return ServerRequest.invokeAccessCodeToToken(getDeployment(), code, redirectUri, null);
     }
 
     /**
@@ -146,7 +136,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     }
 
     public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure {
-        return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, tokenUrl, false), clientId, credentials);
+        return ServerRequest.invokeRefresh(getDeployment(), refreshToken);
     }
 
     public static IDToken extractIdToken(String idToken) {
@@ -167,8 +157,4 @@ public class ServletOAuthClient extends AbstractOAuthClient {
             return url;
         }
     }
-
-    public void setAdapterConfig(AdapterConfig adapterConfig) {
-        this.adapterConfig = adapterConfig;
-    }
 }
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
index 20f33bc..4354f10 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
@@ -1,14 +1,8 @@
 package org.keycloak.servlet;
 
-import org.apache.http.client.HttpClient;
-import org.keycloak.constants.ServiceUrlConstants;
-import org.keycloak.adapters.HttpClientBuilder;
-import org.keycloak.enums.RelativeUrlsUsed;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.representations.adapters.config.AdapterConfig;
-import org.keycloak.util.JsonSerialization;
-import org.keycloak.util.KeycloakUriBuilder;
-
-import java.io.IOException;
 import java.io.InputStream;
 
 /**
@@ -18,62 +12,21 @@ import java.io.InputStream;
 public class ServletOAuthClientBuilder {
 
     public static ServletOAuthClient build(InputStream is) {
-        AdapterConfig adapterConfig = getAdapterConfig(is);
-        return build(adapterConfig);
-    }
-
-    public static AdapterConfig getAdapterConfig(InputStream is) {
-        try {
-            return JsonSerialization.readValue(is, AdapterConfig.class, true);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
+        ServletOAuthClient client = new ServletOAuthClient();
+        client.setDeployment(deployment);
+        return client;
     }
 
     public static ServletOAuthClient build(AdapterConfig adapterConfig) {
-        ServletOAuthClient oauthClient = new ServletOAuthClient();
-        build(adapterConfig, oauthClient);
-        return oauthClient;
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(adapterConfig);
+        ServletOAuthClient client = new ServletOAuthClient();
+        client.setDeployment(deployment);
+        return client;
     }
 
     public static void build(InputStream is, ServletOAuthClient oauthClient) {
-        build(getAdapterConfig(is), oauthClient);
-    }
-
-
-    public static void build(AdapterConfig adapterConfig, ServletOAuthClient oauthClient) {
-        oauthClient.setAdapterConfig(adapterConfig);
-        oauthClient.setClientId(adapterConfig.getResource());
-        oauthClient.setPublicClient(adapterConfig.isPublicClient());
-        oauthClient.setCredentials(adapterConfig.getCredentials());
-        if (adapterConfig.getAuthServerUrl() == null) {
-            throw new RuntimeException("You must specify auth-url");
-        }
-        KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrl());
-        RelativeUrlsUsed useRelative = relativeUrls(serverBuilder, adapterConfig);
-        oauthClient.setRelativeUrlsUsed(useRelative);
-
-        String authUrl = serverBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(adapterConfig.getRealm()).toString();
-
-        KeycloakUriBuilder tokenUrlBuilder;
-
-        if (useRelative == RelativeUrlsUsed.BROWSER_ONLY) {
-            // Use absolute URI for refreshToken and codeToToken requests
-            KeycloakUriBuilder nonBrowsersServerBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrlForBackendRequests());
-            tokenUrlBuilder = nonBrowsersServerBuilder.clone();
-        } else {
-            tokenUrlBuilder = serverBuilder.clone();
-        }
-        String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_PATH).build(adapterConfig.getRealm()).toString();
-        oauthClient.setAuthUrl(authUrl);
-        oauthClient.setTokenUrl(tokenUrl);
-    }
-
-    private static RelativeUrlsUsed relativeUrls(KeycloakUriBuilder serverBuilder, AdapterConfig adapterConfig) {
-        if (serverBuilder.clone().getHost() == null) {
-            return (adapterConfig.getAuthServerUrlForBackendRequests() != null) ? RelativeUrlsUsed.BROWSER_ONLY : RelativeUrlsUsed.ALL_REQUESTS;
-        } else {
-            return RelativeUrlsUsed.NEVER;
-        }
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
+        oauthClient.setDeployment(deployment);
     }
 }
diff --git a/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java b/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java
new file mode 100644
index 0000000..604b58c
--- /dev/null
+++ b/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java
@@ -0,0 +1,25 @@
+package org.keycloak.servlet;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.enums.RelativeUrlsUsed;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ServletOAuthClientBuilderTest {
+
+    @Test
+    public void testBuilder() {
+        ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
+        Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getAuthUrl());
+        Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getTokenUrl());
+        assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
+        Assert.assertEquals("customer-portal", oauthClient.getClientId());
+        Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
+        Assert.assertEquals(true, oauthClient.isPublicClient());
+    }
+}
diff --git a/integration/servlet-oauth-client/src/test/resources/keycloak.json b/integration/servlet-oauth-client/src/test/resources/keycloak.json
new file mode 100644
index 0000000..d952196
--- /dev/null
+++ b/integration/servlet-oauth-client/src/test/resources/keycloak.json
@@ -0,0 +1,28 @@
+{
+  "realm": "demo",
+  "resource": "customer-portal",
+  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url": "https://localhost:8443/auth",
+  "ssl-required": "external",
+  "use-resource-role-mappings": true,
+  "enable-cors": true,
+  "cors-max-age": 1000,
+  "cors-allowed-methods": "POST, PUT, DELETE, GET",
+  "cors-allowed-headers": "X-Custom, X-Custom2",
+  "bearer-only": true,
+  "public-client": true,
+  "enable-basic-auth": true,
+  "expose-token": true,
+  "credentials": {
+    "secret": "234234-234234-234234"
+  },
+  "connection-pool-size": 20,
+  "disable-trust-manager": true,
+  "allow-any-hostname": true,
+  "auth-server-url-for-backend-requests": "https://backend:8443/auth",
+  "always-refresh-token": true,
+  "register-node-at-startup": true,
+  "register-node-period": 1000,
+  "token-store": "cookie",
+  "principal-attribute": "email"
+}
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 8b14f74..9992af3 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -304,7 +304,7 @@ public class DefaultAuthenticationFlows {
         execution = new AuthenticationExecutionModel();
         execution.setParentFlow(clients.getId());
         execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
-        execution.setAuthenticator("client-signed-jwt");
+        execution.setAuthenticator("client-jwt");
         execution.setPriority(20);
         execution.setAuthenticatorFlow(false);
         realm.addAuthenticatorExecution(execution);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index f3dd3b8..b51a39f 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -33,6 +33,7 @@ import org.keycloak.util.Time;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -67,6 +68,7 @@ public class AuthenticationProcessor {
 
     // Used for client authentication
     protected ClientModel client;
+    protected Map<String, String> clientAuthAttributes = new HashMap<>();
 
     public AuthenticationProcessor() {
     }
@@ -83,6 +85,10 @@ public class AuthenticationProcessor {
         this.client = client;
     }
 
+    public Map<String, String> getClientAuthAttributes() {
+        return clientAuthAttributes;
+    }
+
     public ClientSessionModel getClientSession() {
         return clientSession;
     }
@@ -342,6 +348,11 @@ public class AuthenticationProcessor {
         }
 
         @Override
+        public Map<String, String> getClientAuthAttributes() {
+            return AuthenticationProcessor.this.getClientAuthAttributes();
+        }
+
+        @Override
         public ClientSessionModel getClientSession() {
             return AuthenticationProcessor.this.getClientSession();
         }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientAuthUtil.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientAuthUtil.java
index 8fd5d36..dc232e3 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientAuthUtil.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientAuthUtil.java
@@ -29,51 +29,4 @@ public class ClientAuthUtil {
         return Response.status(status).entity(e).type(MediaType.APPLICATION_JSON_TYPE).build();
     }
 
-
-    // Return client either from client_id parameter or from "username" send in "Authorization: Basic" header.
-    public static ClientModel getClientFromClientId(ClientAuthenticationFlowContext context) {
-        String client_id = null;
-        String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
-        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
-
-        if (authorizationHeader != null) {
-            String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
-            if (usernameSecret != null) {
-                client_id = usernameSecret[0];
-            } else {
-
-                // Don't send 401 if client_id parameter was sent in request. For example IE may automatically send "Authorization: Negotiate" in XHR requests even for public clients
-                if (!formData.containsKey(OAuth2Constants.CLIENT_ID)) {
-                    Response challengeResponse = Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getRealm().getName() + "\"").build();
-                    context.challenge(challengeResponse);
-                    return null;
-                }
-            }
-        }
-
-        if (client_id == null) {
-            client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
-        }
-
-        if (client_id == null) {
-            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
-            context.challenge(challengeResponse);
-            return null;
-        }
-
-        context.getEvent().client(client_id);
-
-        ClientModel client = context.getRealm().getClientByClientId(client_id);
-        if (client == null) {
-            context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
-            return null;
-        }
-
-        if (!client.isEnabled()) {
-            context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
-            return null;
-        }
-
-        return client;
-    }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java
index 00bd47f..8bbf190 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java
@@ -8,6 +8,7 @@ import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 
 import org.jboss.logging.Logger;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.ClientAuthenticationFlowContext;
@@ -39,21 +40,60 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator 
 
     @Override
     public void authenticateClient(ClientAuthenticationFlowContext context) {
-        ClientModel client = ClientAuthUtil.getClientFromClientId(context);
+        String client_id = null;
+        String clientSecret = null;
+
+        String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+
+        if (authorizationHeader != null) {
+            String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
+            if (usernameSecret != null) {
+                client_id = usernameSecret[0];
+                clientSecret = usernameSecret[1];
+            } else {
+
+                // Don't send 401 if client_id parameter was sent in request. For example IE may automatically send "Authorization: Negotiate" in XHR requests even for public clients
+                if (!formData.containsKey(OAuth2Constants.CLIENT_ID)) {
+                    Response challengeResponse = Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getRealm().getName() + "\"").build();
+                    context.challenge(challengeResponse);
+                    return;
+                }
+            }
+        }
+
+        if (client_id == null) {
+            client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
+            clientSecret = formData.getFirst("client_secret");
+        }
+
+        if (client_id == null) {
+            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
+            context.challenge(challengeResponse);
+            return;
+        }
+
+        context.getEvent().client(client_id);
+
+        ClientModel client = context.getRealm().getClientByClientId(client_id);
         if (client == null) {
+            context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
+            return;
+        }
+
+        if (!client.isEnabled()) {
+            context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
             return;
-        } else {
-            context.setClient(client);
         }
 
+        context.setClient(client);
+
         // Skip client_secret validation for public client
         if (client.isPublicClient()) {
             context.success();
             return;
         }
 
-        String clientSecret = getClientSecret(context);
-
         if (clientSecret == null) {
             Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client secret not provided in request");
             context.challenge(challengeResponse);
@@ -75,30 +115,6 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator 
         context.success();
     }
 
-    protected String getClientSecret(ClientAuthenticationFlowContext context) {
-        String clientSecret = null;
-        String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
-        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
-
-        if (authorizationHeader != null) {
-            String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
-            if (usernameSecret != null) {
-                clientSecret = usernameSecret[1];
-            }
-        }
-
-        if (clientSecret == null) {
-            clientSecret = formData.getFirst("client_secret");
-        }
-
-        return clientSecret;
-    }
-
-    protected void setError(AuthenticationFlowContext context, Response challengeResponse) {
-        context.getEvent().error(Errors.INVALID_CLIENT_CREDENTIALS);
-        context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
-    }
-
     @Override
     public String getDisplayType() {
         return "Client Id and Secret";
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
index 3a98c8d..3f7ddb6 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
@@ -30,7 +30,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
 
     protected static Logger logger = Logger.getLogger(JWTClientAuthenticator.class);
 
-    public static final String PROVIDER_ID = "client-signed-jwt";
+    public static final String PROVIDER_ID = "client-jwt";
     public static final String CERTIFICATE_ATTR = "jwt.credential.certificate";
 
     public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
diff --git a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java
index 3ef5c28..7c864f1 100644
--- a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java
@@ -55,20 +55,6 @@ public class ClientAuthenticationFlow implements AuthenticationFlow {
                 AuthenticationFlow authenticationFlow;
                 authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
 
-
-                /*if (model.getFlowId() != null) {
-                    authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
-                } else {
-                    // Continue with the flow specific to authenticatedClient
-                    ClientModel authenticatedClient = processor.getClient();
-                    if (authenticatedClient != null) {
-                        String clientFlowId = authenticatedClient.getClientAuthFlowId();
-                        authenticationFlow = processor.createFlowExecution(clientFlowId, model);
-                    } else {
-                        throw new AuthenticationFlowException("Authenticated client required for: " + model.getAuthenticator(), AuthenticationFlowError.CLIENT_NOT_FOUND);
-                    }
-                }*/
-
                 Response flowChallenge = authenticationFlow.processFlow();
                 if (flowChallenge == null) {
                     if (model.isAlternative()) alternativeSuccessful = true;
diff --git a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
index 1d20325..9f11eb9 100644
--- a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
+++ b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
@@ -1,5 +1,7 @@
 package org.keycloak.authentication;
 
+import java.util.Map;
+
 import org.keycloak.models.ClientModel;
 
 /**
@@ -22,4 +24,15 @@ public interface ClientAuthenticationFlowContext extends AbstractAuthenticationF
      */
     void setClient(ClientModel client);
 
+    /**
+     * Return the map where the authenticators can put some additional state related to authenticated client and the context how was
+     * client authenticated (ie. attributes from client certificate etc). Map is writable, so you can add/remove items from it as needed.
+     *
+     * After successful authentication will be those state data put into UserSession notes. This allows you to configure
+     * UserSessionNote protocol mapper for your client, which will allow to map those state data into the access token available in the application
+     *
+     * @return
+     */
+    Map<String, String> getClientAuthAttributes();
+
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 1c74699..bec6aaf 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -190,7 +190,7 @@ public class LogoutEndpoint {
     }
 
     private ClientModel authorizeClient() {
-        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm);
+        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
 
         if (client.isBearerOnly()) {
             throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index dd7235f..15c1f46 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -7,6 +7,7 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.constants.AdapterConstants;
+import org.keycloak.constants.ServiceAccountConstants;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -19,17 +20,16 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.utils.DefaultAuthenticationFlows;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.ServiceAccountManager;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.Cors;
 import org.keycloak.services.Urls;
 
@@ -52,6 +52,7 @@ public class TokenEndpoint {
     private static final Logger logger = Logger.getLogger(TokenEndpoint.class);
     private MultivaluedMap<String, String> formParams;
     private ClientModel client;
+    private Map<String, String> clientAuthAttributes;
 
     private enum Action {
         AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD, CLIENT_CREDENTIALS
@@ -144,7 +145,9 @@ public class TokenEndpoint {
     }
 
     private void checkClient() {
-        client = AuthorizeClientUtil.authorizeClient(session, event, realm);
+        AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
+        client = clientAuth.getClient();
+        clientAuthAttributes = clientAuth.getClientAuthAttributes();
 
         if (client.isBearerOnly()) {
             throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
@@ -237,6 +240,7 @@ public class TokenEndpoint {
         }
 
         updateClientSession(clientSession);
+        updateUserSessionFromClientAuth(userSession);
 
         AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
 
@@ -262,6 +266,7 @@ public class TokenEndpoint {
 
             UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState());
             updateClientSessions(userSession.getClientSessions());
+            updateUserSessionFromClientAuth(userSession);
 
         } catch (OAuthErrorException e) {
             event.error(Errors.INVALID_TOKEN);
@@ -312,6 +317,12 @@ public class TokenEndpoint {
         }
     }
 
+    private void updateUserSessionFromClientAuth(UserSessionModel userSession) {
+        for (Map.Entry<String, String> attr : clientAuthAttributes.entrySet()) {
+            userSession.setNote(attr.getKey(), attr.getValue());
+        }
+    }
+
     public Response buildResourceOwnerPasswordCredentialsGrant() {
         event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
 
@@ -349,6 +360,7 @@ public class TokenEndpoint {
         }
         processor.attachSession();
         UserSessionModel userSession = processor.getUserSession();
+        updateUserSessionFromClientAuth(userSession);
 
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
                 .generateAccessToken(session, scope, client, user, userSession, clientSession)
@@ -363,8 +375,68 @@ public class TokenEndpoint {
     }
 
     public Response buildClientCredentialsGrant() {
-        ServiceAccountManager serviceAccountManager = new ServiceAccountManager(tokenManager, event, request, formParams, session, client);
-        return serviceAccountManager.buildClientCredentialsGrant();
+        if (client.isBearerOnly()) {
+            event.error(Errors.INVALID_CLIENT);
+            throw new ErrorResponseException("unauthorized_client", "Bearer-only client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
+        }
+        if (client.isPublicClient()) {
+            event.error(Errors.INVALID_CLIENT);
+            throw new ErrorResponseException("unauthorized_client", "Public client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
+        }
+        if (!client.isServiceAccountsEnabled()) {
+            event.error(Errors.INVALID_CLIENT);
+            throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
+        }
+
+        event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
+
+        UserModel clientUser = session.users().getUserByServiceAccountClient(client);
+
+        if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
+            // May need to handle bootstrap here as well
+            logger.infof("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId());
+            new ClientManager(new RealmManager(session)).enableServiceAccount(client);
+            clientUser = session.users().getUserByServiceAccountClient(client);
+        }
+
+        String clientUsername = clientUser.getUsername();
+        event.detail(Details.USERNAME, clientUsername);
+        event.user(clientUser);
+
+        if (!clientUser.isEnabled()) {
+            event.error(Errors.USER_DISABLED);
+            throw new ErrorResponseException("invalid_request", "User '" + clientUsername + "' disabled", Response.Status.UNAUTHORIZED);
+        }
+
+        String scope = formParams.getFirst(OAuth2Constants.SCOPE);
+
+        UserSessionProvider sessions = session.sessions();
+
+        ClientSessionModel clientSession = sessions.createClientSession(realm, client);
+        clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
+
+        UserSessionModel userSession = sessions.createUserSession(realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
+        event.session(userSession);
+
+        TokenManager.attachClientSession(userSession, clientSession);
+
+        // Notes about client details
+        userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
+        userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
+        userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
+
+        updateUserSessionFromClientAuth(userSession);
+
+        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
+                .generateAccessToken(session, scope, client, clientUser, userSession, clientSession)
+                .generateRefreshToken()
+                .generateIDToken()
+                .build();
+
+        event.success();
+
+        return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
     }
 
 }
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
index a2b701c..bc7fdb3 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
@@ -1,5 +1,7 @@
 package org.keycloak.protocol.oidc.utils;
 
+import java.util.Map;
+
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.events.EventBuilder;
@@ -17,7 +19,7 @@ import javax.ws.rs.core.Response;
  */
 public class AuthorizeClientUtil {
 
-    public static ClientModel authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
+    public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
         AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
         String flowId = clientAuthFlow.getId();
 
@@ -40,7 +42,26 @@ public class AuthorizeClientUtil {
             throw new ErrorResponseException("invalid_client", "Client authentication was successful, but client is null", Response.Status.BAD_REQUEST);
         }
 
-        return client;
+        return new ClientAuthResult(client, processor.getClientAuthAttributes());
+    }
+
+    public static class ClientAuthResult {
+
+        private final ClientModel client;
+        private final Map<String, String> clientAuthAttributes;
+
+        private ClientAuthResult(ClientModel client, Map<String, String> clientAuthAttributes) {
+            this.client = client;
+            this.clientAuthAttributes = clientAuthAttributes;
+        }
+
+        public ClientModel getClient() {
+            return client;
+        }
+
+        public Map<String, String> getClientAuthAttributes() {
+            return clientAuthAttributes;
+        }
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
index 2b92ccf..4e7d7de 100755
--- a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
+++ b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
@@ -153,7 +153,7 @@ public class ClientsManagementService {
     }
 
     protected ClientModel authorizeClient() {
-        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm);
+        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
 
         if (client.isPublicClient()) {
             Map<String, String> error = new HashMap<String, String>();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
index b65cac6..c3f04bd 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
@@ -1,5 +1,6 @@
 package org.keycloak.testsuite.oauth;
 
+import java.security.PrivateKey;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -17,7 +18,7 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.adapters.ClientAuthAdapterUtils;
+import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
 import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
 import org.keycloak.constants.ServiceAccountConstants;
 import org.keycloak.constants.ServiceUrlConstants;
@@ -238,8 +239,8 @@ public class ClientAuthSignedJWTTest {
 
     @Test
     public void testAssertionMissingIssuer() throws Exception {
-        String invalidJwt = ClientAuthAdapterUtils.createSignedJWT(null, getRealmInfoUrl(),
-                "classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
+        String invalidJwt = getClientSignedJWT(
+                "classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, null);
 
         List<NameValuePair> parameters = new LinkedList<NameValuePair>();
         parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@@ -254,8 +255,8 @@ public class ClientAuthSignedJWTTest {
 
     @Test
     public void testAssertionUnknownClient() throws Exception {
-        String invalidJwt = ClientAuthAdapterUtils.createSignedJWT("unknown-client", getRealmInfoUrl(),
-                "classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
+        String invalidJwt = getClientSignedJWT(
+                "classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "unknown-client");
 
         List<NameValuePair> parameters = new LinkedList<NameValuePair>();
         parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@@ -339,24 +340,8 @@ public class ClientAuthSignedJWTTest {
     @Test
     public void testAssertionInvalidSignature() throws Exception {
         // JWT for client1, but signed by privateKey of client2
-        String invalidJwt =  ClientAuthAdapterUtils.createSignedJWT("client1", getRealmInfoUrl(),
-                "classpath:client-auth-test/keystore-client2.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
-
-        List<NameValuePair> parameters = new LinkedList<NameValuePair>();
-        parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
-        parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
-        parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, invalidJwt));
-
-        HttpResponse resp = sendRequest(oauth.getServiceAccountUrl(), parameters);
-        OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(resp);
-
-        assertError(response, "client1", "unauthorized_client", Errors.INVALID_CLIENT_CREDENTIALS);
-    }
-
-    @Test
-    public void testAssertionInvalidAudience() throws Exception {
-        String invalidJwt = ClientAuthAdapterUtils.createSignedJWT("client1", "invalid-audience",
-                "classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
+        String invalidJwt =  getClientSignedJWT(
+                "classpath:client-auth-test/keystore-client2.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "client1");
 
         List<NameValuePair> parameters = new LinkedList<NameValuePair>();
         parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@@ -491,17 +476,20 @@ public class ClientAuthSignedJWTTest {
 
 
     private String getClient1SignedJWT() {
-        return ClientAuthAdapterUtils.createSignedJWT("client1", getRealmInfoUrl(),
-                "classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
+        return getClientSignedJWT("classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "client1");
     }
 
     private String getClient2SignedJWT() {
-        // keystore-client2.p12 doesn't work on Sun JDK due to restrictions on key length
-        // String keystoreFile = "classpath:client-auth-test/keystore-client2.p12";
+        return getClientSignedJWT("classpath:client-auth-test/keystore-client2.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "client2");
+    }
+
+    private String getClientSignedJWT(String keystoreFile, String storePassword, String keyPassword, String keyAlias, KeystoreUtil.KeystoreFormat format, String clientId) {
+        PrivateKey privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(keystoreFile, storePassword, keyPassword, keyAlias, format);
 
-        String keystoreFile = "classpath:client-auth-test/keystore-client2.jks";
-        return ClientAuthAdapterUtils.createSignedJWT("client2", getRealmInfoUrl(),
-                keystoreFile, "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
+        JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider();
+        jwtProvider.setPrivateKey(privateKey);
+        jwtProvider.setTokenTimeout(10);
+        return jwtProvider.createSignedRequestToken(clientId, getRealmInfoUrl());
     }
 
     private String getRealmInfoUrl() {
diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
index 0e47e0c..af9a559 100755
--- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
@@ -126,7 +126,9 @@
             "redirectUris": [
                 "http://localhost:8081/secure-portal/*"
             ],
-            "secret": "password"
+            "attributes": {
+                "jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
+            }
         },
         {
             "name": "session-portal",
diff --git a/testsuite/integration/src/test/resources/adapter-test/secure-portal-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/secure-portal-keycloak.json
index c479feb..3500790 100755
--- a/testsuite/integration/src/test/resources/adapter-test/secure-portal-keycloak.json
+++ b/testsuite/integration/src/test/resources/adapter-test/secure-portal-keycloak.json
@@ -5,6 +5,13 @@
   "auth-server-url" : "http://localhost:8081/auth",
   "ssl-required" : "external",
   "credentials" : {
-      "secret": "password"
-   }
+    "jwt": {
+      "client-keystore-file": "classpath:adapter-test/secure-portal-keystore.jks",
+      "client-keystore-type": "JKS",
+      "client-keystore-password": "storepass",
+      "client-key-password": "keypass",
+      "client-key-alias": "clientkey",
+      "token-expiration": 10
+    }
+  }
 }
diff --git a/testsuite/integration/src/test/resources/adapter-test/secure-portal-keystore.jks b/testsuite/integration/src/test/resources/adapter-test/secure-portal-keystore.jks
new file mode 100644
index 0000000..9b2a4d6
Binary files /dev/null and b/testsuite/integration/src/test/resources/adapter-test/secure-portal-keystore.jks differ
diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json b/testsuite/tomcat6/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json
index dd38f24..8df199b 100755
--- a/testsuite/tomcat6/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json
+++ b/testsuite/tomcat6/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json
@@ -5,6 +5,13 @@
   "auth-server-url" : "http://localhost:8080/auth",
   "ssl-required" : "external",
   "credentials" : {
-      "secret": "password"
-   }
+    "jwt": {
+      "client-keystore-file": "classpath:adapter-test/secure-portal-keystore.jks",
+      "client-keystore-type": "JKS",
+      "client-keystore-password": "storepass",
+      "client-key-password": "keypass",
+      "client-key-alias": "clientkey",
+      "token-expiration": 10
+    }
+  }
 }