keycloak-memoizeit

Changes

pom.xml 1(+1 -0)

Details

diff --git a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
new file mode 100755
index 0000000..56226e0
--- /dev/null
+++ b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.common.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@SuppressWarnings("serial")
+public class ConcurrentMultivaluedHashMap<K, V> extends ConcurrentHashMap<K, List<V>>
+{
+   public void putSingle(K key, V value)
+   {
+      List<V> list = new CopyOnWriteArrayList<>();
+      list.add(value);
+      put(key, list);
+   }
+
+   public void addAll(K key, V... newValues)
+   {
+      for (V value : newValues)
+      {
+         add(key, value);
+      }
+   }
+
+   public void addAll(K key, List<V> valueList)
+   {
+      for (V value : valueList)
+      {
+         add(key, value);
+      }
+   }
+
+   public void addFirst(K key, V value)
+   {
+      List<V> list = get(key);
+      if (list == null)
+      {
+         add(key, value);
+      }
+      else
+      {
+         list.add(0, value);
+      }
+   }
+   public final void add(K key, V value)
+   {
+      getList(key).add(value);
+   }
+
+
+   public final void addMultiple(K key, Collection<V> values)
+   {
+      getList(key).addAll(values);
+   }
+
+   public V getFirst(K key)
+   {
+      List<V> list = get(key);
+      return list == null ? null : list.get(0);
+   }
+
+   public final List<V> getList(K key)
+   {
+      List<V> list = get(key);
+      if (list == null)
+         put(key, list = new CopyOnWriteArrayList<V>());
+      return list;
+   }
+
+   public void addAll(ConcurrentMultivaluedHashMap<K, V> other)
+   {
+      for (Entry<K, List<V>> entry : other.entrySet())
+      {
+         getList(entry.getKey()).addAll(entry.getValue());
+      }
+   }
+
+}
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
index 5373067..94c80a2 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
@@ -25,6 +25,7 @@
     </resources>
 
     <dependencies>
+        <module name="javax.transaction.api"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
index 95f34b6..8b5632e 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
@@ -33,5 +33,6 @@
         <module name="javax.ws.rs.api"/>
         <module name="org.apache.httpcomponents"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
+        <module name="javax.transaction.api"/>
     </dependencies>
 </module>
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index f6a8c4b..257c617 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -74,6 +74,11 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.transaction</groupId>
+            <artifactId>jboss-transaction-api_1.2_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java
new file mode 100644
index 0000000..9940732
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.cache.infinispan.entities;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractExtendableRevisioned extends AbstractRevisioned {
+    protected ConcurrentHashMap cachedWith = new ConcurrentHashMap();
+
+    public AbstractExtendableRevisioned(Long revision, String id) {
+        super(revision, id);
+    }
+
+    /**
+     * Cache things along with this cachable object
+     *
+     * @return
+     */
+    public ConcurrentHashMap getCachedWith() {
+        return cachedWith;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
index 8916620..ed49ddf 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
@@ -9,6 +9,7 @@ import java.io.Serializable;
 public class AbstractRevisioned implements Revisioned, Serializable {
     private String id;
     private Long revision;
+    private final long cacheTimestamp = System.currentTimeMillis();
 
     public AbstractRevisioned(Long revision, String id) {
         this.revision = revision;
@@ -30,4 +31,12 @@ public class AbstractRevisioned implements Revisioned, Serializable {
         this.revision = revision;
     }
 
+    /**
+     * When was this cached
+     *
+     * @return
+     */
+    public long getCacheTimestamp() {
+        return cacheTimestamp;
+    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index 89049ca..7c24594 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -36,7 +36,7 @@ import java.util.Set;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class CachedUser extends AbstractRevisioned implements InRealm  {
+public class CachedUser extends AbstractExtendableRevisioned implements InRealm  {
     private String realm;
     private String username;
     private Long createdTimestamp;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index d697ad2..ea6698c 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -28,6 +28,7 @@ import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
 import org.keycloak.models.cache.infinispan.entities.CachedUser;
 import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -39,12 +40,13 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class UserAdapter implements UserModel {
+public class UserAdapter implements CachedUserModel {
     protected UserModel updated;
     protected CachedUser cached;
     protected UserCacheSession userProviderCache;
@@ -65,6 +67,22 @@ public class UserAdapter implements UserModel {
             if (updated == null) throw new IllegalStateException("Not found in database");
         }
     }
+
+    @Override
+    public void invalidate() {
+        getDelegateForUpdate();
+    }
+
+    @Override
+    public long getCacheTimestamp() {
+        return cached.getCacheTimestamp();
+    }
+
+    @Override
+    public ConcurrentHashMap getCachedWith() {
+        return cached.getCachedWith();
+    }
+
     @Override
     public String getId() {
         if (updated != null) return updated.getId();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 8a62c33..b38d9aa 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -34,7 +34,9 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.models.cache.UserCache;
 import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
 import org.keycloak.models.cache.infinispan.entities.CachedUser;
 import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
@@ -47,7 +49,7 @@ import java.util.*;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class UserCacheSession implements CacheUserProvider {
+public class UserCacheSession implements UserCache {
     protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
     protected UserCacheManager cache;
     protected KeycloakSession session;
@@ -73,7 +75,6 @@ public class UserCacheSession implements CacheUserProvider {
         cache.clear();
     }
 
-    @Override
     public UserProvider getDelegate() {
         if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
         if (delegate != null) return delegate;
@@ -89,6 +90,20 @@ public class UserCacheSession implements CacheUserProvider {
         if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
     }
 
+    @Override
+    public void evict(RealmModel realm, UserModel user) {
+        if (user instanceof CachedUserModel) {
+            ((CachedUserModel)user).invalidate();
+        } else {
+            invalidations.add(user.getId());
+            if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
+            invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
+            if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
+        }
+    }
+
+
+
     protected void runInvalidations() {
         for (String realmId : realmInvalidations) {
             cache.invalidateRealmUsers(realmId, invalidations);
@@ -147,8 +162,13 @@ public class UserCacheSession implements CacheUserProvider {
             logger.trace("registered for invalidation return delegate");
             return getDelegate().getUserById(id, realm);
         }
+        if (managedUsers.containsKey(id)) {
+            logger.trace("return managedusers");
+            return managedUsers.get(id);
+        }
 
         CachedUser cached = cache.get(id, CachedUser.class);
+        boolean wasCached = cached != null;
         if (cached == null) {
             logger.trace("not cached");
             Long loaded = cache.getCurrentRevision(id);
@@ -157,19 +177,12 @@ public class UserCacheSession implements CacheUserProvider {
                 logger.trace("delegate returning null");
                 return null;
             }
-            if (managedUsers.containsKey(id)) {
-                logger.trace("return managedusers");
-                return managedUsers.get(id);
-            }
-            if (invalidations.contains(id)) return model;
             cached = new CachedUser(loaded, realm, model);
             cache.addRevisioned(cached, startupRevision);
-        } else if (managedUsers.containsKey(id)) {
-            logger.trace("return managedusers");
-            return managedUsers.get(id);
         }
         logger.trace("returning new cache adapter");
         UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+        if (!wasCached) onCache(realm, adapter);
         managedUsers.put(id, adapter);
         return adapter;
     }
@@ -223,13 +236,7 @@ public class UserCacheSession implements CacheUserProvider {
                 return managedUsers.get(userId);
             }
 
-            CachedUser cached = cache.get(userId, CachedUser.class);
-            if (cached == null) {
-                cached = new CachedUser(loaded, realm, model);
-                cache.addRevisioned(cached, startupRevision);
-            }
-            logger.trace("return new cache adapter");
-            UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+            UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
             managedUsers.put(userId, adapter);
             return adapter;
         } else {
@@ -244,6 +251,26 @@ public class UserCacheSession implements CacheUserProvider {
         }
     }
 
+    protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel model) {
+        CachedUser cached = cache.get(userId, CachedUser.class);
+        boolean wasCached = cached != null;
+        if (cached == null) {
+            cached = new CachedUser(loaded, realm, model);
+            cache.addRevisioned(cached, startupRevision);
+        }
+        UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+        if (!wasCached) {
+            onCache(realm, adapter);
+        }
+        return adapter;
+
+    }
+
+    private void onCache(RealmModel realm, UserAdapter adapter) {
+        ((OnUserCache)getDelegate()).onCache(realm, adapter);
+        ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter);
+    }
+
     @Override
     public UserModel getUserByEmail(String email, RealmModel realm) {
         if (email == null) return null;
@@ -268,12 +295,7 @@ public class UserCacheSession implements CacheUserProvider {
             if (invalidations.contains(userId)) return model;
             if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
 
-            CachedUser cached = cache.get(userId, CachedUser.class);
-            if (cached == null) {
-                cached = new CachedUser(loaded, realm, model);
-                cache.addRevisioned(cached, startupRevision);
-            }
-            UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+            UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
             managedUsers.put(userId, adapter);
             return adapter;
         } else {
@@ -316,12 +338,7 @@ public class UserCacheSession implements CacheUserProvider {
             if (invalidations.contains(userId)) return model;
             if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
 
-            CachedUser cached = cache.get(userId, CachedUser.class);
-            if (cached == null) {
-                cached = new CachedUser(loaded, realm, model);
-                cache.addRevisioned(cached, startupRevision);
-            }
-            UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+            UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
             managedUsers.put(userId, adapter);
             return adapter;
         } else {
@@ -669,4 +686,5 @@ public class UserCacheSession implements CacheUserProvider {
         getDelegate().preRemove(realm, component);
 
     }
+
 }
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
index de6e587..4c5ccc4 100755
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
@@ -101,14 +101,19 @@ public class ClusteredCacheBehaviorTest {
 
         System.out.println("node1 create entry");
         node1Cache.put("key", "node1");
+
         System.out.println("node1 create entry");
         node1Cache.put("key", "node111");
+
         System.out.println("node2 create entry");
         node2Cache.put("key", "node2");
+
         System.out.println("node1 remove entry");
         node1Cache.remove("key");
+
         System.out.println("node2 remove entry");
         node2Cache.remove("key");
+
         System.out.println("node2 put entry");
         node2Cache.put("key", "node2");
         System.out.println("node2 evict entry");
@@ -118,6 +123,28 @@ public class ClusteredCacheBehaviorTest {
         node2Cache.putForExternalRead("key", "common");
         System.out.println("node2 remove entry");
         node2Cache.remove("key");
+        System.out.println("node1 remove entry");
+        node1Cache.remove("key");
+
+        // test remove non-existing node 2, existing node 1
+        System.out.println("Test non existent remove");
+        System.out.println("node1 create entry");
+        node1Cache.put("key", "value");
+        System.out.println("node2 remove non-existent entry");
+        System.out.println("exists?: " + node2Cache.containsKey("key"));
+        node2Cache.remove("key");
+
+        // test clear
+        System.out.println("Test clear cache");
+        System.out.println("add key to node 1, key2 to node2");
+        node1Cache.putForExternalRead("key", "value");
+        node2Cache.putForExternalRead("key", "value");
+        node2Cache.putForExternalRead("key2", "value");
+        System.out.println("Clear from node1");
+        node1Cache.clear();
+        System.out.println("node 2 exists key2?: " + node2Cache.containsKey("key2"));
+        System.out.println("node 2 exists key?: " + node2Cache.containsKey("key"));
+
 
 
     }
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
index db85a82..e35e5b0 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.connections.jpa;
 
+import org.jboss.logging.Logger;
+
 import javax.persistence.EntityManager;
 
 /**
@@ -24,6 +26,7 @@ import javax.persistence.EntityManager;
  */
 public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
 
+    private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class);
     private final EntityManager em;
 
     public DefaultJpaConnectionProvider(EntityManager em) {
@@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
 
     @Override
     public void close() {
+        logger.trace("DefaultJpaConnectionProvider close()");
         em.close();
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index 3c74264..d029e16 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -29,12 +29,22 @@ import java.util.Map;
 import javax.naming.InitialContext;
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
+import javax.persistence.SynchronizationType;
 import javax.sql.DataSource;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
 
 import org.hibernate.ejb.AvailableSettings;
+import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
+import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
@@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory;
 import org.keycloak.models.dblock.DBLockManager;
 import org.keycloak.ServerStartupError;
 import org.keycloak.timer.TimerProvider;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -60,16 +71,29 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
     private volatile EntityManagerFactory emf;
 
     private Config.Scope config;
-    
-    private Map<String,String> operationalInfo;
+
+    private Map<String, String> operationalInfo;
+
+    private boolean jtaEnabled;
+    private JtaTransactionManagerLookup jtaLookup;
+
+    private KeycloakSessionFactory factory;
 
     @Override
     public JpaConnectionProvider create(KeycloakSession session) {
+        logger.trace("Create JpaConnectionProvider");
         lazyInit(session);
 
-        EntityManager em = emf.createEntityManager();
+        EntityManager em = null;
+        if (!jtaEnabled) {
+            logger.trace("enlisting EntityManager in JpaKeycloakTransaction");
+            em = emf.createEntityManager();
+        } else {
+
+            em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED);
+        }
         em = PersistenceExceptionConverter.create(em);
-        session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
+        if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
         return new DefaultJpaConnectionProvider(em);
     }
 
@@ -92,85 +116,112 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
 
     @Override
     public void postInit(KeycloakSessionFactory factory) {
+        this.factory = factory;
+        checkJtaEnabled(factory);
 
     }
 
+    protected void checkJtaEnabled(KeycloakSessionFactory factory) {
+        jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
+        if (jtaLookup != null) {
+            if (jtaLookup.getTransactionManager() != null) {
+                jtaEnabled = true;
+            }
+        }
+    }
+
     private void lazyInit(KeycloakSession session) {
         if (emf == null) {
             synchronized (this) {
                 if (emf == null) {
-                    logger.debug("Initializing JPA connections");
+                    KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+                        logger.debug("Initializing JPA connections");
 
-                    Map<String, Object> properties = new HashMap<String, Object>();
+                        Map<String, Object> properties = new HashMap<String, Object>();
 
-                    String unitName = "keycloak-default";
+                        String unitName = "keycloak-default";
 
-                    String dataSource = config.get("dataSource");
-                    if (dataSource != null) {
-                        if (config.getBoolean("jta", false)) {
-                            properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
+                        String dataSource = config.get("dataSource");
+                        if (dataSource != null) {
+                            if (config.getBoolean("jta", jtaEnabled)) {
+                                properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
+                            } else {
+                                properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
+                            }
                         } else {
-                            properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
-                        }
-                    } else {
-                        properties.put(AvailableSettings.JDBC_URL, config.get("url"));
-                        properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
-
-                        String user = config.get("user");
-                        if (user != null) {
-                            properties.put(AvailableSettings.JDBC_USER, user);
-                        }
-                        String password = config.get("password");
-                        if (password != null) {
-                            properties.put(AvailableSettings.JDBC_PASSWORD, password);
-                        }
-                    }
-
-                    String schema = getSchema();
-                    if (schema != null) {
-                        properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
-                    }
-
-                    MigrationStrategy migrationStrategy = getMigrationStrategy();
-                    boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
-                    File databaseUpdateFile = getDatabaseUpdateFile();
-
-                    properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
-                    properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
-
-                    Connection connection = getConnection();
-                    try{ 
-	                    prepareOperationalInfo(connection);
-
-                        String driverDialect = detectDialect(connection);
-                        if (driverDialect != null) {
-                            properties.put("hibernate.dialect", driverDialect);
+                            properties.put(AvailableSettings.JDBC_URL, config.get("url"));
+                            properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
+
+                            String user = config.get("user");
+                            if (user != null) {
+                                properties.put(AvailableSettings.JDBC_USER, user);
+                            }
+                            String password = config.get("password");
+                            if (password != null) {
+                                properties.put(AvailableSettings.JDBC_PASSWORD, password);
+                            }
                         }
 
-                        migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
-
-                        int globalStatsInterval = config.getInt("globalStatsInterval", -1);
-                        if (globalStatsInterval != -1) {
-                            properties.put("hibernate.generate_statistics", true);
+                        String schema = getSchema();
+                        if (schema != null) {
+                            properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
                         }
 
-	                    logger.trace("Creating EntityManagerFactory");
-	                    emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
-	                    logger.trace("EntityManagerFactory created");
-
-                        if (globalStatsInterval != -1) {
-                            startGlobalStats(session, globalStatsInterval);
+                        MigrationStrategy migrationStrategy = getMigrationStrategy();
+                        boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
+                        File databaseUpdateFile = getDatabaseUpdateFile();
+
+                        properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
+                        properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
+
+                        Connection connection = getConnection();
+                        try {
+                            prepareOperationalInfo(connection);
+
+                            String driverDialect = detectDialect(connection);
+                            if (driverDialect != null) {
+                                properties.put("hibernate.dialect", driverDialect);
+                            }
+
+                            migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
+
+                            int globalStatsInterval = config.getInt("globalStatsInterval", -1);
+                            if (globalStatsInterval != -1) {
+                                properties.put("hibernate.generate_statistics", true);
+                            }
+
+                            logger.trace("Creating EntityManagerFactory");
+                            logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled);
+                            if (jtaEnabled) {
+                                properties.put(org.hibernate.cfg.AvailableSettings.JTA_PLATFORM, new AbstractJtaPlatform() {
+                                    @Override
+                                    protected TransactionManager locateTransactionManager() {
+                                        return jtaLookup.getTransactionManager();
+                                    }
+
+                                    @Override
+                                    protected UserTransaction locateUserTransaction() {
+                                        return null;
+                                    }
+                                });
+                            }
+                            emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader(), jtaEnabled);
+                            logger.trace("EntityManagerFactory created");
+
+                            if (globalStatsInterval != -1) {
+                                startGlobalStats(session, globalStatsInterval);
+                            }
+                        } finally {
+                            // Close after creating EntityManagerFactory to prevent in-mem databases from closing
+                            if (connection != null) {
+                                try {
+                                    connection.close();
+                                } catch (SQLException e) {
+                                    logger.warn("Can't close connection", e);
+                                }
+                            }
                         }
-                    } finally {
-	                    // Close after creating EntityManagerFactory to prevent in-mem databases from closing
-	                    if (connection != null) {
-	                        try {
-	                            connection.close();
-	                        } catch (SQLException e) {
-	                            logger.warn("Can't close connection", e);
-	                        }
-	                    }
-                    }
+                    });
                 }
             }
         }
@@ -182,19 +233,19 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
     }
 
     protected void prepareOperationalInfo(Connection connection) {
-  		try {
-  			operationalInfo = new LinkedHashMap<>();
-  			DatabaseMetaData md = connection.getMetaData();
-  			operationalInfo.put("databaseUrl",md.getURL());
-  			operationalInfo.put("databaseUser", md.getUserName());
-  			operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
-  			operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
+        try {
+            operationalInfo = new LinkedHashMap<>();
+            DatabaseMetaData md = connection.getMetaData();
+            operationalInfo.put("databaseUrl", md.getURL());
+            operationalInfo.put("databaseUser", md.getUserName());
+            operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
+            operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
 
             logger.debugf("Database info: %s", operationalInfo.toString());
-  		} catch (SQLException e) {
-  			logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
-  		}
-  	}
+        } catch (SQLException e) {
+            logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
+        }
+    }
 
 
     protected String detectDialect(Connection connection) {
@@ -334,11 +385,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
     public String getSchema() {
         return config.get("schema");
     }
-    
+
     @Override
-  	public Map<String,String> getOperationalInfo() {
-  		return operationalInfo;
-  	}
+    public Map<String, String> getOperationalInfo() {
+        return operationalInfo;
+    }
 
     private MigrationStrategy getMigrationStrategy() {
         String migrationStrategy = config.get("migrationStrategy");
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
index 7c48499..d080542 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
@@ -23,14 +23,13 @@ import java.sql.SQLException;
 import liquibase.Liquibase;
 import liquibase.exception.DatabaseException;
 import liquibase.exception.LiquibaseException;
-import liquibase.exception.LockException;
-import liquibase.lockservice.LockService;
 import org.jboss.logging.Logger;
 import org.keycloak.connections.jpa.JpaConnectionProvider;
 import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
 import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.dblock.DBLockProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
         this.session = session;
     }
 
+
     private void lazyInit() {
         if (!initialized) {
             LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
@@ -92,35 +92,41 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
 
     @Override
     public void waitForLock() {
-        lazyInit();
-
-        while (maxAttempts > 0) {
-            try {
-                lockService.waitForLock();
-                factory.setHasLock(true);
-                this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
-                return;
-            } catch (LockRetryException le) {
-                // Indicates we should try to acquire lock again in different transaction
-                safeRollbackConnection();
-                restart();
-                maxAttempts--;
-            } catch (RuntimeException re) {
-                safeRollbackConnection();
-                safeCloseConnection();
-                throw re;
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+
+            lazyInit();
+
+            while (maxAttempts > 0) {
+                try {
+                    lockService.waitForLock();
+                    factory.setHasLock(true);
+                    this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
+                    return;
+                } catch (LockRetryException le) {
+                    // Indicates we should try to acquire lock again in different transaction
+                    safeRollbackConnection();
+                    restart();
+                    maxAttempts--;
+                } catch (RuntimeException re) {
+                    safeRollbackConnection();
+                    safeCloseConnection();
+                    throw re;
+                }
             }
-        }
+        });
+
     }
 
 
     @Override
     public void releaseLock() {
-        lazyInit();
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+            lazyInit();
 
-        lockService.releaseLock();
-        lockService.reset();
-        factory.setHasLock(false);
+            lockService.releaseLock();
+            lockService.reset();
+            factory.setHasLock(false);
+        });
     }
 
     @Override
@@ -136,21 +142,25 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
 
     @Override
     public void destroyLockInfo() {
-        lazyInit();
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+            lazyInit();
 
-        try {
-            this.lockService.destroy();
-            dbConnection.commit();
-            logger.debug("Destroyed lock table");
-        } catch (DatabaseException | SQLException de) {
-            logger.error("Failed to destroy lock table");
-            safeRollbackConnection();
-        }
+            try {
+                this.lockService.destroy();
+                dbConnection.commit();
+                logger.debug("Destroyed lock table");
+            } catch (DatabaseException | SQLException de) {
+                logger.error("Failed to destroy lock table");
+                safeRollbackConnection();
+            }
+        });
     }
 
     @Override
     public void close() {
-        safeCloseConnection();
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+            safeCloseConnection();
+        });
     }
 
     private void safeRollbackConnection() {
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
index 5ac7d2f..0385f6a 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
@@ -21,6 +21,8 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
 import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
 import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
 import org.hibernate.jpa.boot.spi.Bootstrap;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
 import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
 import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
 import org.keycloak.models.KeycloakSession;
@@ -46,8 +48,9 @@ public class JpaUtils {
         return (schema==null) ? tableName : schema + "." + tableName;
     }
 
-    public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
-        PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
+    public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader, boolean jta) {
+        PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL;
+        PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType);
         List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
         for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
             if (persistenceUnit.getName().equals(unitName)) {
@@ -58,6 +61,7 @@ public class JpaUtils {
                 }
                 // Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
                 // to find and load the extra provided entities. Set the provided classloader as parent classloader.
+                persistenceUnit.setTransactionType(txType);
                 return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
                         new ProxyClassLoader(providedEntities, classLoader)).build();
             }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java
new file mode 100755
index 0000000..f4ceb94
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="getCredentialAttribute", query="select attr from CredentialAttributeEntity attr where attr.credential = :credential"),
+        @NamedQuery(name="deleteCredentialAttributeByCredential", query="delete from  CredentialAttributeEntity attr where attr.credential = :credential"),
+        @NamedQuery(name="deleteCredentialAttributeByRealm", query="delete from  CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId))"),
+        @NamedQuery(name="deleteCredentialAttributeByRealmAndLink", query="delete from  CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
+        @NamedQuery(name="deleteCredentialAttributeByUser", query="delete from  CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user = :user)"),
+})
+@Table(name="CREDENTIAL_ATTRIBUTE")
+@Entity
+public class CredentialAttributeEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "CREDENTIAL_ID")
+    protected CredentialEntity credential;
+
+    @Column(name = "NAME")
+    protected String name;
+    @Column(name = "VALUE")
+    protected String value;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public CredentialEntity getCredential() {
+        return credential;
+    }
+
+    public void setCredential(CredentialEntity credential) {
+        this.credential = credential;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof CredentialAttributeEntity)) return false;
+
+        CredentialAttributeEntity that = (CredentialAttributeEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index ceb284c..4c8f0b3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities;
 
 import javax.persistence.Access;
 import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -27,14 +28,19 @@ import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
 import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 @NamedQueries({
+        @NamedQuery(name="credentialByUser", query="select cred from CredentialEntity cred where cred.user = :user"),
         @NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"),
+        @NamedQuery(name="credentialByNameAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type and cred.device = :device"),
         @NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId)"),
         @NamedQuery(name="deleteCredentialsByRealmAndLink", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
 
@@ -74,6 +80,8 @@ public class CredentialEntity {
     @Column(name="PERIOD")
     protected int period;
 
+    @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
+    protected Collection<CredentialAttributeEntity> credentialAttributes = new ArrayList<>();
 
     public String getId() {
         return id;
@@ -171,6 +179,14 @@ public class CredentialEntity {
         this.period = period;
     }
 
+    public Collection<CredentialAttributeEntity> getCredentialAttributes() {
+        return credentialAttributes;
+    }
+
+    public void setCredentialAttributes(Collection<CredentialAttributeEntity> credentialAttributes) {
+        this.credentialAttributes = credentialAttributes;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java
new file mode 100644
index 0000000..d3539fe
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserCredentialStore implements UserCredentialStore {
+
+    private final KeycloakSession session;
+    protected final EntityManager em;
+
+    public JpaUserCredentialStore(KeycloakSession session, EntityManager em) {
+        this.session = session;
+        this.em = em;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
+        if (entity == null) return;
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+        } else {
+            MultivaluedHashMap<String, String> attrs = cred.getConfig();
+            MultivaluedHashMap<String, String> config = cred.getConfig();
+            if (config == null) config = new MultivaluedHashMap<>();
+
+            Iterator<CredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+            while (it.hasNext()) {
+                CredentialAttributeEntity attr = it.next();
+                List<String> values = config.getList(attr.getName());
+                if (values == null || !values.contains(attr.getValue())) {
+                    em.remove(attr);
+                    it.remove();
+                } else {
+                    attrs.add(attr.getName(), attr.getValue());
+                }
+
+            }
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                List<String> attrValues = attrs.getList(key);
+                for (String val : values) {
+                    if (attrValues == null || !attrValues.contains(val)) {
+                        CredentialAttributeEntity attr = new CredentialAttributeEntity();
+                        attr.setId(KeycloakModelUtils.generateId());
+                        attr.setValue(val);
+                        attr.setName(key);
+                        attr.setCredential(entity);
+                        em.persist(attr);
+                        entity.getCredentialAttributes().add(attr);
+                    }
+                }
+            }
+
+        }
+
+    }
+
+    @Override
+    public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        CredentialEntity entity = new CredentialEntity();
+        String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+        entity.setId(id);
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        UserEntity userRef = em.getReference(UserEntity.class, user.getId());
+        entity.setUser(userRef);
+        em.persist(entity);
+        MultivaluedHashMap<String, String> config = cred.getConfig();
+        if (config != null || !config.isEmpty()) {
+
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                for (String val : values) {
+                    CredentialAttributeEntity attr = new CredentialAttributeEntity();
+                    attr.setId(KeycloakModelUtils.generateId());
+                    attr.setValue(val);
+                    attr.setName(key);
+                    attr.setCredential(entity);
+                    em.persist(attr);
+                    entity.getCredentialAttributes().add(attr);
+                }
+            }
+
+        }
+        return toModel(entity);
+    }
+
+    @Override
+    public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+        CredentialEntity entity = em.find(CredentialEntity.class, id);
+        if (entity == null) return false;
+        em.remove(entity);
+        return true;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+        CredentialEntity entity = em.find(CredentialEntity.class, id);
+        if (entity == null) return null;
+        CredentialModel model = toModel(entity);
+        return model;
+    }
+
+    protected CredentialModel toModel(CredentialEntity entity) {
+        CredentialModel model = new CredentialModel();
+        model.setId(entity.getId());
+        model.setType(entity.getType());
+        model.setValue(entity.getValue());
+        model.setAlgorithm(entity.getAlgorithm());
+        model.setSalt(entity.getSalt());
+        model.setPeriod(entity.getPeriod());
+        model.setCounter(entity.getCounter());
+        model.setCreatedDate(entity.getCreatedDate());
+        model.setDevice(entity.getDevice());
+        model.setDigits(entity.getDigits());
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        model.setConfig(config);
+        for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+            config.add(attr.getName(), attr.getValue());
+        }
+        return model;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+        UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+        TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
+                .setParameter("user", userEntity);
+        List<CredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (CredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+        UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+        TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("user", userEntity);
+        List<CredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (CredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+        TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("device", name)
+                .setParameter("user", userEntity);
+        List<CredentialEntity> results = query.getResultList();
+        if (results.isEmpty()) return null;
+        return toModel(results.get(0));
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java
new file mode 100755
index 0000000..550299b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderFactory;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserCredentialStoreFactory implements ProviderFactory<UserCredentialStore> {
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return "jpa";
+    }
+
+    @Override
+    public UserCredentialStore create(KeycloakSession session) {
+        EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+        return new JpaUserCredentialStore(session, em);
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 0d938d6..d44c6fe 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -17,7 +17,11 @@
 
 package org.keycloak.models.jpa;
 
+import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
@@ -34,6 +38,8 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
+import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
 import org.keycloak.models.jpa.entities.UserAttributeEntity;
 import org.keycloak.models.jpa.entities.UserConsentEntity;
@@ -48,7 +54,9 @@ import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -58,7 +66,7 @@ import java.util.Set;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class JpaUserProvider implements UserProvider {
+public class JpaUserProvider implements UserProvider, UserCredentialStore {
 
     private static final String EMAIL = "email";
     private static final String USERNAME = "username";
@@ -367,6 +375,8 @@ public class JpaUserProvider implements UserProvider {
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteFederatedIdentityByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteCredentialAttributeByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteCredentialsByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUserAttributesByRealm")
@@ -391,6 +401,10 @@ public class JpaUserProvider implements UserProvider {
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
                 .executeUpdate();
+        num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
         num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
@@ -716,4 +730,174 @@ public class JpaUserProvider implements UserProvider {
     public void preRemove(RealmModel realm, ComponentModel component) {
 
     }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
+        if (entity == null) return;
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+        } else {
+            MultivaluedHashMap<String, String> attrs = cred.getConfig();
+            MultivaluedHashMap<String, String> config = cred.getConfig();
+            if (config == null) config = new MultivaluedHashMap<>();
+
+            Iterator<CredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+            while (it.hasNext()) {
+                CredentialAttributeEntity attr = it.next();
+                List<String> values = config.getList(attr.getName());
+                if (values == null || !values.contains(attr.getValue())) {
+                    em.remove(attr);
+                    it.remove();
+                } else {
+                    attrs.add(attr.getName(), attr.getValue());
+                }
+
+            }
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                List<String> attrValues = attrs.getList(key);
+                for (String val : values) {
+                    if (attrValues == null || !attrValues.contains(val)) {
+                        CredentialAttributeEntity attr = new CredentialAttributeEntity();
+                        attr.setId(KeycloakModelUtils.generateId());
+                        attr.setValue(val);
+                        attr.setName(key);
+                        attr.setCredential(entity);
+                        em.persist(attr);
+                        entity.getCredentialAttributes().add(attr);
+                    }
+                }
+            }
+
+        }
+
+    }
+
+    @Override
+    public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        CredentialEntity entity = new CredentialEntity();
+        String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+        entity.setId(id);
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        UserEntity userRef = em.getReference(UserEntity.class, user.getId());
+        entity.setUser(userRef);
+        em.persist(entity);
+        MultivaluedHashMap<String, String> config = cred.getConfig();
+        if (config != null || !config.isEmpty()) {
+
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                for (String val : values) {
+                    CredentialAttributeEntity attr = new CredentialAttributeEntity();
+                    attr.setId(KeycloakModelUtils.generateId());
+                    attr.setValue(val);
+                    attr.setName(key);
+                    attr.setCredential(entity);
+                    em.persist(attr);
+                    entity.getCredentialAttributes().add(attr);
+                }
+            }
+
+        }
+        return toModel(entity);
+    }
+
+    @Override
+    public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+        CredentialEntity entity = em.find(CredentialEntity.class, id);
+        if (entity == null) return false;
+        em.remove(entity);
+        return true;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+        CredentialEntity entity = em.find(CredentialEntity.class, id);
+        if (entity == null) return null;
+        CredentialModel model = toModel(entity);
+        return model;
+    }
+
+    protected CredentialModel toModel(CredentialEntity entity) {
+        CredentialModel model = new CredentialModel();
+        model.setId(entity.getId());
+        model.setType(entity.getType());
+        model.setValue(entity.getValue());
+        model.setAlgorithm(entity.getAlgorithm());
+        model.setSalt(entity.getSalt());
+        model.setPeriod(entity.getPeriod());
+        model.setCounter(entity.getCounter());
+        model.setCreatedDate(entity.getCreatedDate());
+        model.setDevice(entity.getDevice());
+        model.setDigits(entity.getDigits());
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        model.setConfig(config);
+        for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+            config.add(attr.getName(), attr.getValue());
+        }
+        return model;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+        UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+        TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
+                .setParameter("user", userEntity);
+        List<CredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (CredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+        UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+        TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("user", userEntity);
+        List<CredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (CredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+        TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("device", name)
+                .setParameter("user", userEntity);
+        List<CredentialEntity> results = query.getResultList();
+        if (results.isEmpty()) return null;
+        return toModel(results.get(0));
+    }
+
+
+
+
 }
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java
new file mode 100755
index 0000000..c6a15b3
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.CredentialEntity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="deleteFederatedCredentialAttributeByCredential", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential = :credential"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByStorageProvider", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId)"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByRealm", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.realmId=:realmId)"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByRealmAndLink", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
+        @NamedQuery(name="deleteFederatedCredentialAttributeByUser", query="delete from  FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId)"),
+})
+@Table(name="FED_CREDENTIAL_ATTRIBUTE")
+@Entity
+public class FederatedUserCredentialAttributeEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "CREDENTIAL_ID")
+    protected FederatedUserCredentialEntity credential;
+
+    @Column(name = "NAME")
+    protected String name;
+    @Column(name = "VALUE")
+    protected String value;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public FederatedUserCredentialEntity getCredential() {
+        return credential;
+    }
+
+    public void setCredential(FederatedUserCredentialEntity credential) {
+        this.credential = credential;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserCredentialAttributeEntity)) return false;
+
+        FederatedUserCredentialAttributeEntity that = (FederatedUserCredentialAttributeEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
index 996608b..52a757c 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
@@ -17,10 +17,12 @@
 
 package org.keycloak.storage.jpa.entity;
 
+import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
 
 import javax.persistence.Access;
 import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -29,7 +31,10 @@ import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
 import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -38,6 +43,7 @@ import javax.persistence.Table;
 @NamedQueries({
         @NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
         @NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+        @NamedQuery(name="federatedUserCredentialByNameAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
         @NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"),
         @NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
         @NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
@@ -87,6 +93,8 @@ public class FederatedUserCredentialEntity {
     protected int digits;
     @Column(name="PERIOD")
     protected int period;
+    @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
+    protected Collection<FederatedUserCredentialAttributeEntity> credentialAttributes = new ArrayList<>();
 
 
     public String getId() {
@@ -201,6 +209,14 @@ public class FederatedUserCredentialEntity {
         this.period = period;
     }
 
+    public Collection<FederatedUserCredentialAttributeEntity> getCredentialAttributes() {
+        return credentialAttributes;
+    }
+
+    public void setCredentialAttributes(Collection<FederatedUserCredentialAttributeEntity> credentialAttributes) {
+        this.credentialAttributes = credentialAttributes;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java
new file mode 100644
index 0000000..13fa032
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaFederatedUserCredentialStore implements UserCredentialStore {
+
+    private final KeycloakSession session;
+    protected final EntityManager em;
+
+    public JpaFederatedUserCredentialStore(KeycloakSession session, EntityManager em) {
+        this.session = session;
+        this.em = em;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+        if (entity == null) return;
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+        } else {
+            MultivaluedHashMap<String, String> attrs = cred.getConfig();
+            MultivaluedHashMap<String, String> config = cred.getConfig();
+            if (config == null) config = new MultivaluedHashMap<>();
+
+            Iterator<FederatedUserCredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+            while (it.hasNext()) {
+                FederatedUserCredentialAttributeEntity attr = it.next();
+                List<String> values = config.getList(attr.getName());
+                if (values == null || !values.contains(attr.getValue())) {
+                    em.remove(attr);
+                    it.remove();
+                } else {
+                    attrs.add(attr.getName(), attr.getValue());
+                }
+
+            }
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                List<String> attrValues = attrs.getList(key);
+                for (String val : values) {
+                    if (attrValues == null || !attrValues.contains(val)) {
+                        FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+                        attr.setId(KeycloakModelUtils.generateId());
+                        attr.setValue(val);
+                        attr.setName(key);
+                        attr.setCredential(entity);
+                        em.persist(attr);
+                        entity.getCredentialAttributes().add(attr);
+                    }
+                }
+            }
+
+        }
+
+    }
+
+    @Override
+    public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
+        String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+        entity.setId(id);
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        entity.setUserId(user.getId());
+        entity.setRealmId(realm.getId());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        em.persist(entity);
+        MultivaluedHashMap<String, String> config = cred.getConfig();
+        if (config != null || !config.isEmpty()) {
+
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                for (String val : values) {
+                    FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+                    attr.setId(KeycloakModelUtils.generateId());
+                    attr.setValue(val);
+                    attr.setName(key);
+                    attr.setCredential(entity);
+                    em.persist(attr);
+                    entity.getCredentialAttributes().add(attr);
+                }
+            }
+
+        }
+        return toModel(entity);
+    }
+
+    @Override
+    public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+        if (entity == null) return false;
+        em.remove(entity);
+        return true;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+        if (entity == null) return null;
+        CredentialModel model = toModel(entity);
+        return model;
+    }
+
+    protected CredentialModel toModel(FederatedUserCredentialEntity entity) {
+        CredentialModel model = new CredentialModel();
+        model.setId(entity.getId());
+        model.setType(entity.getType());
+        model.setValue(entity.getValue());
+        model.setAlgorithm(entity.getAlgorithm());
+        model.setSalt(entity.getSalt());
+        model.setPeriod(entity.getPeriod());
+        model.setCounter(entity.getCounter());
+        model.setCreatedDate(entity.getCreatedDate());
+        model.setDevice(entity.getDevice());
+        model.setDigits(entity.getDigits());
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        model.setConfig(config);
+        for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+            config.add(attr.getName(), attr.getValue());
+        }
+        return model;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (FederatedUserCredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (FederatedUserCredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("device", name)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        if (results.isEmpty()) return null;
+        return toModel(results.get(0));
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
index f6710dd..1c72da0 100644
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -18,6 +18,8 @@ package org.keycloak.storage.jpa;
 
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.GroupModel;
@@ -49,6 +51,7 @@ import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
 import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
 import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
 import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
 import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
 import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
 import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
@@ -59,6 +62,7 @@ import javax.persistence.TypedQuery;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -75,7 +79,8 @@ public class JpaUserFederatedStorageProvider implements
         UserCredentialsFederatedStorage,
         UserGroupMembershipFederatedStorage,
         UserRequiredActionsFederatedStorage,
-        UserRoleMappingsFederatedStorage {
+        UserRoleMappingsFederatedStorage,
+        UserCredentialStore {
 
     private final KeycloakSession session;
     protected EntityManager em;
@@ -607,6 +612,200 @@ public class JpaUserFederatedStorageProvider implements
     }
 
     @Override
+    public boolean removeCredential(RealmModel realm, String id) {
+        return false;
+    }
+
+    @Override
+    public CredentialModel getCredentialById(String id) {
+        return null;
+    }
+
+    @Override
+    public List<CredentialModel> getCredentials(RealmModel realm) {
+        return null;
+    }
+
+    @Override
+    public List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user) {
+        return null;
+    }
+
+    @Override
+    public List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type) {
+        return null;
+    }
+
+    @Override
+    public CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        return null;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+        if (entity == null) return;
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+        } else {
+            MultivaluedHashMap<String, String> attrs = cred.getConfig();
+            MultivaluedHashMap<String, String> config = cred.getConfig();
+            if (config == null) config = new MultivaluedHashMap<>();
+
+            Iterator<FederatedUserCredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+            while (it.hasNext()) {
+                FederatedUserCredentialAttributeEntity attr = it.next();
+                List<String> values = config.getList(attr.getName());
+                if (values == null || !values.contains(attr.getValue())) {
+                    em.remove(attr);
+                    it.remove();
+                } else {
+                    attrs.add(attr.getName(), attr.getValue());
+                }
+
+            }
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                List<String> attrValues = attrs.getList(key);
+                for (String val : values) {
+                    if (attrValues == null || !attrValues.contains(val)) {
+                        FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+                        attr.setId(KeycloakModelUtils.generateId());
+                        attr.setValue(val);
+                        attr.setName(key);
+                        attr.setCredential(entity);
+                        em.persist(attr);
+                        entity.getCredentialAttributes().add(attr);
+                    }
+                }
+            }
+
+        }
+
+    }
+
+    @Override
+    public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
+        String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+        entity.setId(id);
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        entity.setCreatedDate(cred.getCreatedDate());
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        entity.setUserId(user.getId());
+        entity.setRealmId(realm.getId());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        em.persist(entity);
+        MultivaluedHashMap<String, String> config = cred.getConfig();
+        if (config != null || !config.isEmpty()) {
+
+            for (String key : config.keySet()) {
+                List<String> values = config.getList(key);
+                for (String val : values) {
+                    FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+                    attr.setId(KeycloakModelUtils.generateId());
+                    attr.setValue(val);
+                    attr.setName(key);
+                    attr.setCredential(entity);
+                    em.persist(attr);
+                    entity.getCredentialAttributes().add(attr);
+                }
+            }
+
+        }
+        return toModel(entity);
+    }
+
+    @Override
+    public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+        if (entity == null) return false;
+        em.remove(entity);
+        return true;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+        if (entity == null) return null;
+        CredentialModel model = toModel(entity);
+        return model;
+    }
+
+    protected CredentialModel toModel(FederatedUserCredentialEntity entity) {
+        CredentialModel model = new CredentialModel();
+        model.setId(entity.getId());
+        model.setType(entity.getType());
+        model.setValue(entity.getValue());
+        model.setAlgorithm(entity.getAlgorithm());
+        model.setSalt(entity.getSalt());
+        model.setPeriod(entity.getPeriod());
+        model.setCounter(entity.getCounter());
+        model.setCreatedDate(entity.getCreatedDate());
+        model.setDevice(entity.getDevice());
+        model.setDigits(entity.getDigits());
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        model.setConfig(config);
+        for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+            config.add(attr.getName(), attr.getValue());
+        }
+        return model;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (FederatedUserCredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        List<CredentialModel> rtn = new LinkedList<>();
+        for (FederatedUserCredentialEntity entity : results) {
+            rtn.add(toModel(entity));
+        }
+        return rtn;
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
+                .setParameter("type", type)
+                .setParameter("device", name)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        if (results.isEmpty()) return null;
+        return toModel(results.get(0));
+    }
+
+    @Override
     public void preRemove(RealmModel realm) {
         int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
@@ -620,6 +819,8 @@ public class JpaUserFederatedStorageProvider implements
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteBrokerLinkByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
@@ -642,6 +843,10 @@ public class JpaUserFederatedStorageProvider implements
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
                 .executeUpdate();
+        num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
         num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
                 .setParameter("realmId", realm.getId())
                 .setParameter("link", link.getId())
@@ -699,6 +904,10 @@ public class JpaUserFederatedStorageProvider implements
                 .setParameter("userId", user.getId())
                 .setParameter("realmId", realm.getId())
                 .executeUpdate();
+        em.createNamedQuery("deleteFederatedCredentialAttributeByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
         em.createNamedQuery("deleteFederatedUserCredentialByUser")
                 .setParameter("userId", user.getId())
                 .setParameter("realmId", realm.getId())
@@ -737,6 +946,9 @@ public class JpaUserFederatedStorageProvider implements
         em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
                 .setParameter("storageProviderId", model.getId())
                 .executeUpdate();
+        em.createNamedQuery("deleteFederatedCredentialAttributeByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
         em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
                 .setParameter("storageProviderId", model.getId())
                 .executeUpdate();
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
index 63afbb2..a9b6078 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
@@ -24,4 +24,39 @@
         </addColumn>
     </changeSet>
 
+    <changeSet author="bburke@redhat.com" id="2.2.0">
+        <createTable tableName="CREDENTIAL_ATTRIBUTE">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="CREDENTIAL_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(4000)"/>
+        </createTable>
+
+        <createTable tableName="FED_CREDENTIAL_ATTRIBUTE">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="CREDENTIAL_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(4000)"/>
+        </createTable>
+        <modifyDataType tableName="CREDENTIAL" columnName="VALUE" newDataType="VARCHAR(4000)"/>
+
+        <addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="FED_CREDENTIAL_ATTRIBUTE" constraintName="FK_FED_CRED_ATTR" referencedColumnNames="ID" referencedTableName="FED_USER_CREDENTIAL"/>
+        <addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="CREDENTIAL_ATTRIBUTE" constraintName="FK_CRED_ATTR" referencedColumnNames="ID" referencedTableName="CREDENTIAL"/>
+
+
+    </changeSet>
+
+
 </databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 0b2ff23..6288fe5 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -22,6 +22,7 @@
     <persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
         <class>org.keycloak.models.jpa.entities.ClientEntity</class>
         <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.CredentialAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
@@ -74,6 +75,7 @@
         <class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
         <class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index c0537ba..9ad2c1e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -24,6 +24,7 @@ import com.mongodb.QueryBuilder;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.connections.mongo.api.MongoStore;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
@@ -635,4 +636,5 @@ public class MongoUserProvider implements UserProvider {
     public void preRemove(RealmModel realm, ComponentModel component) {
 
     }
+
 }

pom.xml 1(+1 -0)

diff --git a/pom.xml b/pom.xml
index 48e8a65..a2de58f 100755
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
+
 -->
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java
new file mode 100644
index 0000000..7a0393a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.component;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.storage.UserStorageProviderModel;
+
+import java.util.Comparator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PrioritizedComponentModel extends ComponentModel {
+    public static Comparator<ComponentModel> comparator = new Comparator<ComponentModel>() {
+        @Override
+        public int compare(ComponentModel o1, ComponentModel o2) {
+            return parsePriority(o1) - parsePriority(o2);
+        }
+    };
+
+    public PrioritizedComponentModel(ComponentModel copy) {
+        super(copy);
+    }
+
+    public PrioritizedComponentModel() {
+    }
+
+    public static int parsePriority(ComponentModel component) {
+        String priority = component.getConfig().getFirst("priority");
+        if (priority == null) return 0;
+        return Integer.valueOf(priority);
+
+    }
+
+    public int getPriority() {
+        return parsePriority(this);
+
+    }
+
+    public void setPriority(int priority) {
+        getConfig().putSingle("priority", Integer.toString(priority));
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java
new file mode 100644
index 0000000..805fb25
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInput {
+    String getType();
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
new file mode 100644
index 0000000..3456f8f
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInputUpdater {
+    boolean supportsCredentialType(String credentialType);
+    boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+    void disableCredentialType(RealmModel realm, UserModel user, String credentialType);
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
new file mode 100644
index 0000000..a7a4c6d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInputValidator {
+    boolean supportsCredentialType(String credentialType);
+    boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType);
+    boolean isValid(RealmModel realm, UserModel user, CredentialInput input);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
new file mode 100755
index 0000000..24b7772
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.credential;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CredentialModel implements Serializable {
+    public static final String PASSWORD = "password";
+    public static final String PASSWORD_HISTORY = "password-history";
+    public static final String PASSWORD_TOKEN = "password-token";
+
+    // Secret is same as password but it is not hashed
+    public static final String SECRET = "secret";
+    public static final String TOTP = "totp";
+    public static final String HOTP = "hotp";
+    public static final String CLIENT_CERT = "cert";
+    public static final String KERBEROS = "kerberos";
+    public static final String OTP = "otp";
+
+
+
+    private String id;
+    private String type;
+    private String value;
+    private String device;
+    private byte[] salt;
+    private int hashIterations;
+    private Long createdDate;
+
+    // otp stuff
+    private int counter;
+    private String algorithm;
+    private int digits;
+    private int period;
+    private MultivaluedHashMap<String, String> config;
+
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public void setSalt(byte[] salt) {
+        this.salt = salt;
+    }
+
+    public int getHashIterations() {
+        return hashIterations;
+    }
+
+    public void setHashIterations(int iterations) {
+        this.hashIterations = iterations;
+    }
+
+    public Long getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(Long createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public int getCounter() {
+        return counter;
+    }
+
+    public void setCounter(int counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
+
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
+
+    public MultivaluedHashMap<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(MultivaluedHashMap<String, String> config) {
+        this.config = config;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java
new file mode 100644
index 0000000..a830433
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialProvider extends Provider {
+    @Override
+    default
+    void close() {
+
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java
new file mode 100755
index 0000000..480dd1c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.credential;
+
+import org.keycloak.Config;
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.storage.UserStorageProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialProviderFactory<T extends CredentialProvider> extends ComponentFactory<T, CredentialProvider> {
+    /**
+     * called per Keycloak transaction.
+     *
+     * @param session
+     * @param model
+     * @return
+     */
+    T create(KeycloakSession session, ComponentModel model);
+
+    /**
+     * This is the name of the provider and will be showed in the admin console as an option.
+     *
+     * @return
+     */
+    @Override
+    String getId();
+
+    @Override
+    default void init(Config.Scope config) {
+
+    }
+
+    @Override
+    default void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    default void close() {
+
+    }
+
+    @Override
+    default String getHelpText() {
+        return "";
+    }
+
+    @Override
+    default List<ProviderConfigProperty> getConfigProperties() {
+        return Collections.EMPTY_LIST;
+    }
+
+    @Override
+    default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException {
+
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
new file mode 100644
index 0000000..c998739
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialStore extends Provider {
+    void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    boolean removeStoredCredential(RealmModel realm, UserModel user, String id);
+    CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id);
+    List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user);
+    List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type);
+    CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
new file mode 100644
index 0000000..5d9c7cd
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.cache;
+
+import org.keycloak.models.UserModel;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CachedUserModel extends UserModel {
+    void invalidate();
+
+    /**
+     * When was the user loaded from database.
+     *
+     * @return
+     */
+    long getCacheTimestamp();
+
+    /**
+     * Returns a map that contains custom things that are cached along with the user.  You can write to this map.
+     *
+     * @return
+     */
+    ConcurrentHashMap getCachedWith();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
index c6723fb..a0efa39 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
@@ -39,11 +39,11 @@ public class CacheUserProviderSpi implements Spi {
 
     @Override
     public Class<? extends Provider> getProviderClass() {
-        return CacheUserProvider.class;
+        return UserCache.class;
     }
 
     @Override
     public Class<? extends ProviderFactory> getProviderFactoryClass() {
-        return CacheUserProviderFactory.class;
+        return UserCacheProviderFactory.class;
     }
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
new file mode 100755
index 0000000..f309079
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCache extends UserProvider {
+    /**
+     * Evict user from cache.
+     *
+     * @param user
+     */
+    void evict(RealmModel realm, UserModel user);
+    void clear();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 8b6dcd3..2a1ad0f 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models;
 
+import org.keycloak.models.cache.UserCache;
 import org.keycloak.provider.Provider;
 import org.keycloak.scripting.ScriptingProvider;
 import org.keycloak.storage.federated.UserFederatedStorageProvider;
@@ -74,6 +75,7 @@ public interface KeycloakSession {
     Object removeAttribute(String attribute);
     void setAttribute(String name, Object value);
 
+
     void enlistForClose(Provider provider);
 
     KeycloakSessionFactory getKeycloakSessionFactory();
@@ -101,7 +103,14 @@ public interface KeycloakSession {
     void close();
 
     /**
-     * A cached view of all users in system.
+     * The user cache
+     *
+     * @return may be null if cache is disabled
+     */
+    UserCache getUserCache();
+
+    /**
+     * A possibly cached view of all users in system.
      *
      * @return
      */
@@ -115,8 +124,10 @@ public interface KeycloakSession {
      */
     UserProvider userStorageManager();
 
+    UserCredentialManager userCredentialManager();
+
     /**
-     *  A cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI.
+     *  A possibly cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI.
      */
     UserProvider userStorage();
 
@@ -129,6 +140,7 @@ public interface KeycloakSession {
 
     /**
      * Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage.
+     * No cache in front.
      *
      * @return
      */
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
index 0e2dcbe..a18d8cf 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
@@ -23,6 +23,21 @@ package org.keycloak.models;
  */
 public interface KeycloakTransactionManager extends KeycloakTransaction {
 
+    enum JTAPolicy {
+        /**
+         * Do not interact with JTA at all
+         *
+         */
+        NOT_SUPPORTED,
+        /**
+         * A new JTA Transaction will be created when Keycloak TM begin() is called.  If an existing JTA transaction
+         * exists, it is suspended and resumed after the Keycloak transaction finishes.
+         */
+        REQUIRES_NEW,
+    }
+
+    JTAPolicy getJTAPolicy();
+    void setJTAPolicy(JTAPolicy policy);
     void enlist(KeycloakTransaction transaction);
     void enlistAfterCompletion(KeycloakTransaction transaction);
 
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
new file mode 100644
index 0000000..2da5716
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.UserCredentialStore;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialManager extends UserCredentialStore {
+    boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs);
+
+    void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+    void disableCredential(RealmModel realm, UserModel user, String credentialType);
+
+    boolean isConfiguredFor(RealmModel realm, UserModel user, String type);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
index 3e03508..4be355d 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -17,23 +17,26 @@
 
 package org.keycloak.models;
 
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialModel;
+
 import java.util.UUID;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class UserCredentialModel {
-    public static final String PASSWORD = "password";
-    public static final String PASSWORD_HISTORY = "password-history";
-    public static final String PASSWORD_TOKEN = "password-token";
+public class UserCredentialModel implements CredentialInput {
+    public static final String PASSWORD = CredentialModel.PASSWORD;
+    public static final String PASSWORD_HISTORY = CredentialModel.PASSWORD_HISTORY;
+    public static final String PASSWORD_TOKEN = CredentialModel.PASSWORD_TOKEN;
 
     // Secret is same as password but it is not hashed
-    public static final String SECRET = "secret";
-    public static final String TOTP = "totp";
-    public static final String HOTP = "hotp";
-    public static final String CLIENT_CERT = "cert";
-    public static final String KERBEROS = "kerberos";
+    public static final String SECRET = CredentialModel.SECRET;
+    public static final String TOTP = CredentialModel.TOTP;
+    public static final String HOTP = CredentialModel.HOTP;
+    public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT;
+    public static final String KERBEROS = CredentialModel.KERBEROS;
 
     protected String type;
     protected String value;
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index cb6867a..e103705 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -19,6 +19,7 @@ package org.keycloak.models;
 
 import org.jboss.logging.Logger;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.policy.PasswordPolicyManagerProvider;
 import org.keycloak.policy.PolicyError;
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
index d6ef2cd..642131b 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,6 +18,7 @@
 package org.keycloak.models;
 
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
 import org.keycloak.provider.Provider;
 import org.keycloak.storage.user.UserCredentialValidatorProvider;
 import org.keycloak.storage.user.UserLookupProvider;
@@ -85,4 +86,5 @@ public interface UserProvider extends Provider,
     void close();
 
     void preRemove(RealmModel realm, ComponentModel component);
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index bc20d49..b8adc62 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -44,8 +44,14 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CertificateRepresentation;
 import org.keycloak.common.util.CertificateUtils;
 import org.keycloak.common.util.PemUtils;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
 
 import javax.crypto.spec.SecretKeySpec;
+import javax.naming.InitialContext;
+import javax.sql.DataSource;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.security.Key;
@@ -56,6 +62,7 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
+import java.sql.DriverManager;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -63,6 +70,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Function;
 
 /**
  * Set of helper methods, which are useful in various model implementations.
@@ -303,6 +311,7 @@ public final class KeycloakModelUtils {
         }
     }
 
+
     public static String getMasterRealmAdminApplicationClientId(String realmName) {
         return realmName + "-realm";
     }
@@ -651,4 +660,33 @@ public final class KeycloakModelUtils {
             }
         }
     }
+
+    public static void suspendJtaTransaction(KeycloakSessionFactory factory, Runnable runnable) {
+        JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)factory.getProviderFactory(JtaTransactionManagerLookup.class);
+        Transaction suspended = null;
+        try {
+            if (lookup != null) {
+                if (lookup.getTransactionManager() != null) {
+                    try {
+                        suspended = lookup.getTransactionManager().suspend();
+                    } catch (SystemException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+            runnable.run();
+        } finally {
+            if (suspended != null) {
+                try {
+                    lookup.getTransactionManager().resume(suspended);
+                } catch (InvalidTransactionException e) {
+                    throw new RuntimeException(e);
+                } catch (SystemException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+        }
+
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
index 7239182..2202f62 100644
--- a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.storage.federated;
 
+import org.keycloak.credential.CredentialModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
@@ -28,8 +29,20 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public interface UserCredentialsFederatedStorage {
+    // deprecated
     void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
     void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
     void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
     List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
+
+    // new
+    void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
+    boolean removeCredential(RealmModel realm, String id);
+    CredentialModel getCredentialById(String id);
+    List<CredentialModel> getCredentials(RealmModel realm);
+    List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user);
+    List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type);
+    CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
index 351107f..42ad397 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
@@ -17,14 +17,8 @@
 
 package org.keycloak.storage;
 
-import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
-import org.keycloak.models.RealmModel;
-
-import java.io.Serializable;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
+import org.keycloak.component.PrioritizedComponentModel;
 
 /**
  * Stored configuration of a User Storage provider instance.
@@ -32,14 +26,7 @@ import java.util.Map;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
  */
-public class UserStorageProviderModel extends ComponentModel {
-
-    public static Comparator<UserStorageProviderModel> comparator = new Comparator<UserStorageProviderModel>() {
-        @Override
-        public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) {
-            return o1.getPriority() - o2.getPriority();
-        }
-    };
+public class UserStorageProviderModel extends PrioritizedComponentModel {
 
     public UserStorageProviderModel() {
         setProviderType(UserStorageProvider.class.getName());
@@ -49,14 +36,4 @@ public class UserStorageProviderModel extends ComponentModel {
         super(copy);
     }
 
-    public int getPriority() {
-        String priority = getConfig().getFirst("priority");
-        if (priority == null) return 0;
-        return Integer.valueOf(priority);
-
-    }
-
-    public void setPriority(int priority) {
-        getConfig().putSingle("priority", Integer.toString(priority));
-    }
 }
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index d2431d8..5ab0346 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -61,4 +61,5 @@ org.keycloak.authorization.AuthorizationSpi
 org.keycloak.models.cache.authorization.CachedStoreFactorySpi
 org.keycloak.protocol.oidc.TokenIntrospectionSpi
 org.keycloak.policy.PasswordPolicySpi
-org.keycloak.policy.PasswordPolicyManagerSpi
\ No newline at end of file
+org.keycloak.policy.PasswordPolicyManagerSpi
+org.keycloak.transaction.TransactionManagerLookupSpi
diff --git a/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java
new file mode 100644
index 0000000..6d9baf0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.models.utils.TimeBasedOTP;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache {
+    private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class);
+
+    protected KeycloakSession session;
+
+    protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
+        if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
+        CachedUserModel cached = (CachedUserModel)user;
+        List<CredentialModel> rtn = (List<CredentialModel>)cached.getCachedWith().get(LocalOTPCredentialManager.class.getName() + "." + type);
+        if (rtn == null) return Collections.EMPTY_LIST;
+        return rtn;
+    }
+
+    protected UserCredentialStore getCredentialStore() {
+        return session.userCredentialManager();
+    }
+
+    @Override
+    public void onCache(RealmModel realm, CachedUserModel user) {
+        List<CredentialModel> creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+        user.getCachedWith().put(LocalOTPCredentialManager.class.getName() + "." + CredentialModel.TOTP, creds);
+
+    }
+
+    public LocalOTPCredentialManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        if (!supportsCredentialType(input.getType())) return false;
+
+        if (!(input instanceof UserCredentialModel)) {
+            logger.debug("Expected instance of UserCredentialModel for CredentialInput");
+            return false;
+        }
+        UserCredentialModel inputModel = (UserCredentialModel)input;
+        CredentialModel model = null;
+        if (inputModel.getDevice() != null) {
+            model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.TOTP);
+            if (model == null) {
+                model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.HOTP);
+            }
+        }
+        if (model == null) {
+            // delete all existing
+            disableCredentialType(realm, user, CredentialModel.OTP);
+            model = new CredentialModel();
+        }
+
+        OTPPolicy policy = realm.getOTPPolicy();
+        model.setDigits(policy.getDigits());
+        model.setCounter(policy.getInitialCounter());
+        model.setAlgorithm(policy.getAlgorithm());
+        model.setType(policy.getType());
+        model.setValue(inputModel.getValue());
+        model.setDevice(inputModel.getDevice());
+        model.setPeriod(policy.getPeriod());
+        if (model.getId() == null) {
+            getCredentialStore().createCredential(realm, user, model);
+        } else {
+            getCredentialStore().updateCredential(realm, user, model);
+        }
+        session.getUserCache().evict(realm, user);
+        return true;
+
+
+
+    }
+
+    @Override
+    public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+        boolean disableTOTP = false, disableHOTP = false;
+        if (CredentialModel.OTP.equals(credentialType)) {
+            disableTOTP = true;
+            disableHOTP = true;
+        } else if (CredentialModel.HOTP.equals(credentialType)) {
+            disableHOTP = true;
+
+        } else if (CredentialModel.TOTP.equals(credentialType)) {
+            disableTOTP = true;
+        }
+        if (disableHOTP) {
+            List<CredentialModel> hotp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP);
+            for (CredentialModel cred : hotp) {
+                getCredentialStore().removeStoredCredential(realm, user, cred.getId());
+            }
+
+        }
+        if (disableTOTP) {
+            List<CredentialModel> totp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+            if (!totp.isEmpty()) {
+                for (CredentialModel cred : totp) {
+                    getCredentialStore().removeStoredCredential(realm, user, cred.getId());
+                }
+            }
+
+        }
+        if (disableTOTP || disableHOTP) {
+            session.getUserCache().evict(realm, user);
+        }
+    }
+
+    @Override
+    public boolean supportsCredentialType(String credentialType) {
+        return CredentialModel.OTP.equals(credentialType)
+                || CredentialModel.HOTP.equals(credentialType)
+                || CredentialModel.TOTP.equals(credentialType);
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+        if (!supportsCredentialType(credentialType)) return false;
+        if (CredentialModel.OTP.equals(credentialType)) {
+            if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
+                return configuredForHOTP(realm, user);
+            } else {
+                return configuredForTOTP(realm, user);
+            }
+        } else if (CredentialModel.HOTP.equals(credentialType)) {
+            return configuredForHOTP(realm, user);
+
+        } else if (CredentialModel.TOTP.equals(credentialType)) {
+            return configuredForTOTP(realm, user);
+        } else {
+            return false;
+        }
+
+    }
+
+    protected boolean configuredForHOTP(RealmModel realm, UserModel user) {
+        return !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP).isEmpty();
+    }
+
+    protected boolean configuredForTOTP(RealmModel realm, UserModel user) {
+        return !getCachedCredentials(user, CredentialModel.TOTP).isEmpty()
+                || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty();
+    }
+
+    public static boolean validOTP(RealmModel realm, String token, String secret) {
+        OTPPolicy policy = realm.getOTPPolicy();
+        if (policy.getType().equals(UserCredentialModel.TOTP)) {
+            TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+            return validator.validateTOTP(token, secret.getBytes());
+        } else {
+            HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+            int c = validator.validateHOTP(token, secret, policy.getInitialCounter());
+            return c > -1;
+        }
+
+    }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+        if (! (input instanceof UserCredentialModel)) {
+            logger.debug("Expected instance of UserCredentialModel for CredentialInput");
+            return false;
+
+        }
+        String token = ((UserCredentialModel)input).getValue();
+        OTPPolicy policy = realm.getOTPPolicy();
+        if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
+            HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+            for (CredentialModel cred : getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP)) {
+                int counter = validator.validateHOTP(token, cred.getValue(), cred.getCounter());
+                if (counter < 0) continue;
+                cred.setCounter(counter);
+                getCredentialStore().updateCredential(realm, user, cred);
+                return true;
+            }
+        } else {
+            TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+            List<CredentialModel> creds = getCachedCredentials(user, CredentialModel.TOTP);
+            if (creds.isEmpty()) {
+                creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+            } else {
+                logger.debugv("Cache hit for TOTP for user {0}", user.getUsername());
+            }
+            for (CredentialModel cred : creds) {
+                if (validator.validateTOTP(token, cred.getValue().getBytes())) {
+                    return true;
+                }
+            }
+
+        }
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
new file mode 100644
index 0000000..5f67768
--- /dev/null
+++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.credential;
+
+import org.keycloak.common.util.reflections.Types;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.PrioritizedComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialManager;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.UserStorageManager;
+import org.keycloak.storage.UserStorageProvider;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserCredentialStoreManager implements UserCredentialManager, OnUserCache {
+    protected KeycloakSession session;
+
+    public UserCredentialStoreManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+    protected UserCredentialStore getStoreForUser(UserModel user) {
+        if (StorageId.isLocalStorage(user)) {
+            return (UserCredentialStore)session.userLocalStorage();
+        } else {
+            return (UserCredentialStore)session.userFederatedStorage();
+        }
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        getStoreForUser(user).updateCredential(realm, user, cred);
+
+    }
+
+    @Override
+    public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+        return getStoreForUser(user).createCredential(realm, user, cred);
+    }
+
+    @Override
+    public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+        return getStoreForUser(user).removeStoredCredential(realm, user, id);
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+        return getStoreForUser(user).getStoredCredentialById(realm, user, id);
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+        return getStoreForUser(user).getStoredCredentials(realm, user);
+    }
+
+    @Override
+    public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+        return getStoreForUser(user).getStoredCredentialsByType(realm, user, type);
+    }
+
+    @Override
+    public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+        return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
+    }
+
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+
+        List<CredentialInput> toValidate = new LinkedList<>();
+        toValidate.addAll(inputs);
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+            if (provider instanceof CredentialInputValidator) {
+                Iterator<CredentialInput> it = toValidate.iterator();
+                while (it.hasNext()) {
+                    CredentialInput input = it.next();
+                    CredentialInputValidator validator = (CredentialInputValidator)provider;
+                    if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+
+        if (toValidate.isEmpty()) return true;
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
+            Iterator<CredentialInput> it = toValidate.iterator();
+            while (it.hasNext()) {
+                CredentialInput input = it.next();
+                CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+                if (validator == null) {
+                    validator = (CredentialInputValidator)factory.create(session, component);
+                    session.setAttribute(component.getId(), validator);
+                }
+                if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+                    it.remove();
+                }
+            }
+        }
+
+        return toValidate.isEmpty();
+    }
+
+    protected List<ComponentModel> getCredentialProviderComponents(RealmModel realm) {
+        List<ComponentModel> components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
+        if (components.isEmpty()) return components;
+        List<ComponentModel> copy = new LinkedList<>();
+        copy.addAll(components);
+        Collections.sort(copy, PrioritizedComponentModel.comparator);
+        return copy;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+            if (provider instanceof CredentialInputUpdater) {
+                CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+                if (updater.supportsCredentialType(input.getType())) {
+                    if (updater.updateCredential(realm, user, input)) return;
+                }
+            }
+        }
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+            CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+            if (updater == null) {
+                updater = (CredentialInputUpdater)factory.create(session, component);
+                session.setAttribute(component.getId(), updater);
+            }
+            if (!updater.supportsCredentialType(input.getType())) continue;
+            if (updater.updateCredential(realm, user, input)) return;
+        }
+
+    }
+    @Override
+    public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+            if (provider instanceof CredentialInputUpdater) {
+                CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+                if (updater.supportsCredentialType(credentialType)) {
+                    updater.disableCredentialType(realm, user, credentialType);
+                }
+            }
+        }
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+            CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+            if (updater == null) {
+                updater = (CredentialInputUpdater)factory.create(session, component);
+                session.setAttribute(component.getId(), updater);
+            }
+            if (!updater.supportsCredentialType(credentialType)) continue;
+            updater.disableCredentialType(realm, user, credentialType);
+        }
+
+    }
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+        if (!StorageId.isLocalStorage(user)) {
+            String providerId = StorageId.resolveProviderId(user);
+            UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+            if (provider instanceof CredentialInputValidator) {
+                CredentialInputValidator validator = (CredentialInputValidator)provider;
+                if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+                    return true;
+                }
+            }
+        }
+
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+            CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+            if (validator == null) {
+                validator = (CredentialInputValidator)factory.create(session, component);
+                session.setAttribute(component.getId(), validator);
+            }
+            if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+                return true;
+            }
+        }
+        return false;
+
+    }
+
+    @Override
+    public void onCache(RealmModel realm, CachedUserModel user) {
+        List<ComponentModel> components = getCredentialProviderComponents(realm);
+        for (ComponentModel component : components) {
+            CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+            if (!Types.supports(OnUserCache.class, factory, CredentialProviderFactory.class)) continue;
+            OnUserCache validator = (OnUserCache)session.getAttribute(component.getId());
+            if (validator == null) {
+                validator = (OnUserCache)factory.create(session, component);
+                session.setAttribute(component.getId(), validator);
+            }
+            validator.onCache(realm, user);
+        }
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 1fae787..b1ae4dd 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -16,15 +16,18 @@
  */
 package org.keycloak.services;
 
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.credential.UserCredentialStoreManager;
 import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.UserCache;
 import org.keycloak.provider.Provider;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.scripting.ScriptingProvider;
 import org.keycloak.storage.UserStorageManager;
 import org.keycloak.storage.federated.UserFederatedStorageProvider;
 
+import javax.transaction.TransactionManager;
 import java.util.*;
 
 /**
@@ -40,6 +43,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
     private RealmProvider model;
     private UserProvider userModel;
     private UserStorageManager userStorageManager;
+    private UserCredentialStoreManager userCredentialStorageManager;
     private ScriptingProvider scriptingProvider;
     private UserSessionProvider sessionProvider;
     private UserFederationManager federationManager;
@@ -48,7 +52,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
 
     public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
         this.factory = factory;
-        this.transactionManager = new DefaultKeycloakTransactionManager();
+        this.transactionManager = new DefaultKeycloakTransactionManager(this);
         federationManager = new UserFederationManager(this);
         context = new DefaultKeycloakContext(this);
     }
@@ -68,7 +72,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
     }
 
     private UserProvider getUserProvider() {
-        CacheUserProvider cache = getProvider(CacheUserProvider.class);
+        UserCache cache = getProvider(UserCache.class);
         if (cache != null) {
             return cache;
         } else {
@@ -77,6 +81,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
     }
 
     @Override
+    public UserCache getUserCache() {
+        return getProvider(UserCache.class);
+
+    }
+
+    @Override
     public void enlistForClose(Provider provider) {
         closable.add(provider);
     }
@@ -126,6 +136,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
     }
 
     @Override
+    public UserCredentialManager userCredentialManager() {
+        if (userCredentialStorageManager == null) userCredentialStorageManager = new UserCredentialStoreManager(this);
+        return userCredentialStorageManager;
+    }
+
+    @Override
     public UserProvider userStorage() {
         if (userModel == null) {
             userModel = getUserProvider();
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 45bef3c..d5474dc 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -49,7 +49,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
     private Map<Class<? extends Provider>, String> provider = new HashMap<>();
     private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
     protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
-    private TransactionManager tm;
 
     // TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
     protected long serverStartupTimestamp;
@@ -97,8 +96,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
         // make the session factory ready for hot deployment
         ProviderManagerRegistry.SINGLETON.setDeployer(this);
 
-        JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class);
-        if (lookup != null) tm = lookup.getTransactionManager();
     }
     protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
         Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
@@ -282,9 +279,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
 
     public KeycloakSession create() {
         KeycloakSession session =  new DefaultKeycloakSession(this);
-        if (tm != null) {
-            session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
-        }
         return session;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 53f92f4..81379a1 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -16,9 +16,13 @@
  */
 package org.keycloak.services;
 
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.KeycloakTransactionManager;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
+import org.keycloak.transaction.JtaTransactionWrapper;
 
+import javax.transaction.TransactionManager;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -34,6 +38,12 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
     private boolean active;
     private boolean rollback;
+    private KeycloakSession session;
+    private JTAPolicy jtaPolicy = JTAPolicy.REQUIRES_NEW;
+
+    public DefaultKeycloakTransactionManager(KeycloakSession session) {
+        this.session = session;
+    }
 
     @Override
     public void enlist(KeycloakTransaction transaction) {
@@ -63,11 +73,30 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     }
 
     @Override
+    public JTAPolicy getJTAPolicy() {
+        return jtaPolicy;
+    }
+
+    @Override
+    public void setJTAPolicy(JTAPolicy policy) {
+        jtaPolicy = policy;
+
+    }
+
+    @Override
     public void begin() {
         if (active) {
              throw new IllegalStateException("Transaction already active");
         }
 
+        if (jtaPolicy == JTAPolicy.REQUIRES_NEW) {
+            JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
+            TransactionManager tm = jtaLookup.getTransactionManager();
+            if (tm != null) {
+                enlist(new JtaTransactionWrapper(tm));
+            }
+        }
+
         for (KeycloakTransaction tx : transactions) {
             tx.begin();
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 4fd941b..7d49223 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -34,8 +34,6 @@ import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.exportimport.ClientDescriptionConverterFactory;
-import org.keycloak.jose.jws.JWSBuilder;
-import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
@@ -44,7 +42,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.UserCache;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
@@ -55,7 +53,6 @@ import org.keycloak.representations.idm.AdminEventRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.GroupRepresentation;
-import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.PartialImportRepresentation;
 import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -67,7 +64,6 @@ import org.keycloak.services.ServicesLogger;
 import org.keycloak.services.managers.UsersSyncManager;
 import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.resources.admin.RealmAuth.Resource;
-import org.keycloak.timer.TimerProvider;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -85,8 +81,6 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -866,7 +860,7 @@ public class RealmAdminResource {
     public void clearUserCache() {
         auth.requireManage();
 
-        CacheUserProvider cache = session.getProvider(CacheUserProvider.class);
+        UserCache cache = session.getProvider(UserCache.class);
         if (cache != null) {
             cache.clear();
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 3986988..26daf84 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -184,6 +184,8 @@ public class UsersResource {
             return ErrorResponse.exists("User is read only!");
         } catch (ModelException me) {
             return ErrorResponse.exists("Could not update user!");
+        } catch (Exception me) { // JPA may be committed by JTA which can't 
+            return ErrorResponse.exists("Could not update user!");
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 6cdf52e..68e0806 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -45,10 +45,13 @@ import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
 import org.keycloak.services.util.JsonConfigProvider;
 import org.keycloak.services.util.ObjectMapperResolver;
 import org.keycloak.timer.TimerProvider;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.common.util.SystemEnvProperties;
 
 import javax.servlet.ServletContext;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.UriInfo;
@@ -155,11 +158,28 @@ public class KeycloakApplication extends Application {
     // Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
     protected ExportImportManager migrateAndBootstrap() {
         ExportImportManager exportImportManager;
+        logger.debug("Calling migrateModel");
         migrateModel();
 
+        logger.debug("bootstrap");
         KeycloakSession session = sessionFactory.create();
         try {
             session.getTransactionManager().begin();
+            JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class);
+            if (lookup != null) {
+                if (lookup.getTransactionManager() != null) {
+                    try {
+                        Transaction transaction = lookup.getTransactionManager().getTransaction();
+                        logger.debugv("bootstrap current transaction? {0}", transaction != null);
+                        if (transaction != null) {
+                            logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus());
+                        }
+                    } catch (SystemException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+
 
             ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
             exportImportManager = new ExportImportManager(session);
diff --git a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
index ecc3071..e387ff4 100644
--- a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
+++ b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
@@ -16,7 +16,9 @@
  */
 package org.keycloak.transaction;
 
+import org.jboss.logging.Logger;
 import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.storage.UserStorageManager;
 
 import javax.transaction.InvalidTransactionException;
 import javax.transaction.NotSupportedException;
@@ -31,16 +33,22 @@ import javax.transaction.UserTransaction;
  * @version $Revision: 1 $
  */
 public class JtaTransactionWrapper implements KeycloakTransaction {
+    private static final Logger logger = Logger.getLogger(JtaTransactionWrapper.class);
     protected TransactionManager tm;
     protected Transaction ut;
     protected Transaction suspended;
+    protected Exception ended;
 
     public JtaTransactionWrapper(TransactionManager tm) {
         this.tm = tm;
         try {
+
             suspended = tm.suspend();
+            logger.debug("new JtaTransactionWrapper");
+            logger.debugv("was existing? {0}", suspended != null);
             tm.begin();
             ut = tm.getTransaction();
+            //ended = new Exception();
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -53,16 +61,20 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public void commit() {
         try {
-            ut.commit();
+            logger.debug("JtaTransactionWrapper  commit");
+            tm.commit();
         } catch (Exception e) {
             throw new RuntimeException(e);
+        } finally {
+            end();
         }
     }
 
     @Override
     public void rollback() {
         try {
-            ut.rollback();
+            logger.debug("JtaTransactionWrapper rollback");
+            tm.rollback();
         } catch (Exception e) {
             throw new RuntimeException(e);
         } finally {
@@ -74,7 +86,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public void setRollbackOnly() {
         try {
-            ut.setRollbackOnly();
+            tm.setRollbackOnly();
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -83,7 +95,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public boolean getRollbackOnly() {
         try {
-            return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
+            return tm.getStatus() == Status.STATUS_MARKED_ROLLBACK;
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -92,15 +104,28 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public boolean isActive() {
         try {
-            return ut.getStatus() == Status.STATUS_ACTIVE;
+            return tm.getStatus() == Status.STATUS_ACTIVE;
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
+    /*
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (ended != null) {
+            logger.error("TX didn't close at position", ended);
+        }
+
+    }
+    */
 
     protected void end() {
+        ended = null;
+        logger.debug("JtaTransactionWrapper end");
         if (suspended != null) {
             try {
+                logger.debug("JtaTransactionWrapper resuming suspended");
                 tm.resume(suspended);
             } catch (Exception e) {
                 throw new RuntimeException(e);
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 77cba5e..55b31a0 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -18,5 +18,4 @@
 org.keycloak.exportimport.ClientDescriptionConverterSpi
 org.keycloak.wellknown.WellKnownSpi
 org.keycloak.services.clientregistration.ClientRegistrationSpi
-org.keycloak.transaction.TransactionManagerLookupSpi
 
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
index 326fa7f..c823e4f 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
@@ -29,7 +29,7 @@
                     <password>sa</password>
                 </security>
             </datasource>
-            <datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+            <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
                 <?KEYCLOAK_DS_CONNECTION_URL?>
                 <driver>h2</driver>
                 <security>