keycloak-uncached

Merge pull request #4515 from mposolda/jwe JWE, KEYCLOAK-5007

9/29/2017 12:04:59 PM

Changes

Details

diff --git a/common/src/main/java/org/keycloak/common/util/KeyUtils.java b/common/src/main/java/org/keycloak/common/util/KeyUtils.java
index 37e2b2a..932417e 100644
--- a/common/src/main/java/org/keycloak/common/util/KeyUtils.java
+++ b/common/src/main/java/org/keycloak/common/util/KeyUtils.java
@@ -40,8 +40,8 @@ public class KeyUtils {
     private KeyUtils() {
     }
 
-    public static SecretKey loadSecretKey(byte[] secret) {
-        return new SecretKeySpec(secret, "HmacSHA256");
+    public static SecretKey loadSecretKey(byte[] secret, String javaAlgorithmName) {
+        return new SecretKeySpec(secret, javaAlgorithmName);
     }
 
     public static KeyPair generateRsaKeyPair(int keysize) {
diff --git a/common/src/test/java/org/keycloak/common/util/KeyUtilsTest.java b/common/src/test/java/org/keycloak/common/util/KeyUtilsTest.java
index 5e0abf5..29526ea 100644
--- a/common/src/test/java/org/keycloak/common/util/KeyUtilsTest.java
+++ b/common/src/test/java/org/keycloak/common/util/KeyUtilsTest.java
@@ -16,7 +16,7 @@ public class KeyUtilsTest {
         byte[] secretBytes = new byte[32];
         ThreadLocalRandom.current().nextBytes(secretBytes);
         SecretKeySpec expected = new SecretKeySpec(secretBytes, "HmacSHA256");
-        SecretKey actual = KeyUtils.loadSecretKey(secretBytes);
+        SecretKey actual = KeyUtils.loadSecretKey(secretBytes, "HmacSHA256");
         assertEquals(expected.getAlgorithm(), actual.getAlgorithm());
         assertArrayEquals(expected.getEncoded(), actual.getEncoded());
     }
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/AesKeyWrapAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/AesKeyWrapAlgorithmProvider.java
new file mode 100644
index 0000000..0a5a2c5
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/AesKeyWrapAlgorithmProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 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.jose.jwe.alg;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.engines.AESWrapEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
+
+    @Override
+    public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
+        Wrapper encrypter = new AESWrapEngine();
+        encrypter.init(false, new KeyParameter(encryptionKey.getEncoded()));
+        return encrypter.unwrap(encodedCek, 0, encodedCek.length);
+    }
+
+    @Override
+    public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception {
+        Wrapper encrypter = new AESWrapEngine();
+        encrypter.init(true, new KeyParameter(encryptionKey.getEncoded()));
+        byte[] cekBytes = keyStorage.getCekBytes();
+        return encrypter.wrap(cekBytes, 0, cekBytes.length);
+    }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
new file mode 100644
index 0000000..b1dd699
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 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.jose.jwe.alg;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DirectAlgorithmProvider implements JWEAlgorithmProvider {
+
+    @Override
+    public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) {
+        return new byte[0];
+    }
+
+    @Override
+    public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) {
+        return new byte[0];
+    }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
new file mode 100644
index 0000000..caede13
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 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.jose.jwe.alg;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface JWEAlgorithmProvider {
+
+    byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception;
+
+    byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception;
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java
new file mode 100644
index 0000000..dcec260
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2017 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.jose.jwe.enc;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.JWEUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AesCbcHmacShaEncryptionProvider implements JWEEncryptionProvider {
+
+
+    @Override
+    public void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException {
+
+        byte[] contentBytes = jwe.getContent();
+
+        byte[] initializationVector = JWEUtils.generateSecret(16);
+
+        Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+        if (aesKey == null) {
+            throw new IllegalArgumentException("AES CEK key not present");
+        }
+
+        Key hmacShaKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
+        if (hmacShaKey == null) {
+            throw new IllegalArgumentException("HMAC CEK key not present");
+        }
+
+        int expectedAesKeyLength = getExpectedAesKeyLength();
+        if (expectedAesKeyLength != aesKey.getEncoded().length) {
+            throw new IllegalStateException("Length of aes key should be " + expectedAesKeyLength +", but was " + aesKey.getEncoded().length);
+        }
+
+        byte[] cipherBytes = encryptBytes(contentBytes, initializationVector, aesKey);
+
+        byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
+        byte[] authenticationTag = computeAuthenticationTag(aad, initializationVector, cipherBytes, hmacShaKey);
+
+        jwe.setEncryptedContentInfo(initializationVector, cipherBytes, authenticationTag);
+    }
+
+
+    @Override
+    public void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException {
+        Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+        if (aesKey == null) {
+            throw new IllegalArgumentException("AES CEK key not present");
+        }
+
+        Key hmacShaKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
+        if (hmacShaKey == null) {
+            throw new IllegalArgumentException("HMAC CEK key not present");
+        }
+
+        int expectedAesKeyLength = getExpectedAesKeyLength();
+        if (expectedAesKeyLength != aesKey.getEncoded().length) {
+            throw new IllegalStateException("Length of aes key should be " + expectedAesKeyLength +", but was " + aesKey.getEncoded().length);
+        }
+
+        byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
+        byte[] authenticationTag = computeAuthenticationTag(aad, jwe.getInitializationVector(), jwe.getEncryptedContent(), hmacShaKey);
+
+        byte[] expectedAuthTag = jwe.getAuthenticationTag();
+        boolean digitsEqual = MessageDigest.isEqual(expectedAuthTag, authenticationTag);
+
+        if (!digitsEqual) {
+            throw new IllegalArgumentException("Signature validations failed");
+        }
+
+        byte[] contentBytes = decryptBytes(jwe.getEncryptedContent(), jwe.getInitializationVector(), aesKey);
+
+        jwe.content(contentBytes);
+    }
+
+
+    protected abstract int getExpectedAesKeyLength();
+
+    protected abstract String getHmacShaAlgorithm();
+
+    protected abstract int getAuthenticationTagLength();
+
+
+    private byte[] encryptBytes(byte[] contentBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+        AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
+        cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParamSpec);
+        return cipher.doFinal(contentBytes);
+    }
+
+
+    private byte[] decryptBytes(byte[] encryptedBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+        AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
+        cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParamSpec);
+        return cipher.doFinal(encryptedBytes);
+    }
+
+
+    private byte[] computeAuthenticationTag(byte[] aadBytes, byte[] ivBytes, byte[] cipherBytes, Key hmacKeySpec) throws NoSuchAlgorithmException, InvalidKeyException {
+        // Compute "al"
+        ByteBuffer b = ByteBuffer.allocate(4);
+        b.order(ByteOrder.BIG_ENDIAN); // optional, the initial order of a byte buffer is always BIG_ENDIAN.
+        int aadLengthInBits = aadBytes.length * 8;
+        b.putInt(aadLengthInBits);
+        byte[] result1 = b.array();
+        byte[] al = new byte[8];
+        System.arraycopy(result1, 0, al, 4, 4);
+
+        byte[] concatenatedHmacInput = new byte[aadBytes.length + ivBytes.length + cipherBytes.length + al.length];
+        System.arraycopy(aadBytes, 0, concatenatedHmacInput, 0, aadBytes.length);
+        System.arraycopy(ivBytes, 0, concatenatedHmacInput, aadBytes.length, ivBytes.length );
+        System.arraycopy(cipherBytes, 0, concatenatedHmacInput, aadBytes.length + ivBytes.length , cipherBytes.length);
+        System.arraycopy(al, 0, concatenatedHmacInput, aadBytes.length + ivBytes.length + cipherBytes.length, al.length);
+
+        String hmacShaAlg = getHmacShaAlgorithm();
+        Mac macImpl = Mac.getInstance(hmacShaAlg);
+        macImpl.init(hmacKeySpec);
+        macImpl.update(concatenatedHmacInput);
+        byte[] macEncoded =  macImpl.doFinal();
+
+        int authTagLength = getAuthenticationTagLength();
+        return Arrays.copyOf(macEncoded, authTagLength);
+    }
+
+
+    @Override
+    public void deserializeCEK(JWEKeyStorage keyStorage) {
+        byte[] cekBytes = keyStorage.getCekBytes();
+
+        int cekLength = getExpectedCEKLength();
+        byte[] cekMacKey = Arrays.copyOf(cekBytes, cekLength / 2);
+        byte[] cekAesKey = Arrays.copyOfRange(cekBytes, cekLength / 2, cekLength);
+
+        SecretKeySpec aesKey = new SecretKeySpec(cekAesKey, "AES");
+        SecretKeySpec hmacKey = new SecretKeySpec(cekMacKey, "HMACSHA2");
+
+        keyStorage.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION);
+        keyStorage.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+    }
+
+
+    @Override
+    public byte[] serializeCEK(JWEKeyStorage keyStorage) {
+        Key aesKey = keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+        if (aesKey == null) {
+            throw new IllegalArgumentException("AES CEK key not present");
+        }
+
+        Key hmacShaKey = keyStorage.getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
+        if (hmacShaKey == null) {
+            throw new IllegalArgumentException("HMAC CEK key not present");
+        }
+
+        byte[] hmacBytes = hmacShaKey.getEncoded();
+        byte[] aesBytes = aesKey.getEncoded();
+
+        byte[] result = new byte[hmacBytes.length + aesBytes.length];
+        System.arraycopy(hmacBytes, 0, result, 0, hmacBytes.length);
+        System.arraycopy(aesBytes, 0, result, hmacBytes.length, aesBytes.length);
+
+        return result;
+    }
+
+
+
+    public static class Aes128CbcHmacSha256Provider extends AesCbcHmacShaEncryptionProvider {
+
+        @Override
+        protected int getExpectedAesKeyLength() {
+            return 16;
+        }
+
+        @Override
+        protected String getHmacShaAlgorithm() {
+            return "HMACSHA256";
+        }
+
+        @Override
+        protected int getAuthenticationTagLength() {
+            return 16;
+        }
+
+        @Override
+        public int getExpectedCEKLength() {
+            return 32;
+        }
+    }
+
+
+    public static class Aes192CbcHmacSha384Provider extends AesCbcHmacShaEncryptionProvider {
+
+        @Override
+        protected int getExpectedAesKeyLength() {
+            return 24;
+        }
+
+        @Override
+        protected String getHmacShaAlgorithm() {
+            return "HMACSHA384";
+        }
+
+        @Override
+        protected int getAuthenticationTagLength() {
+            return 24;
+        }
+
+        @Override
+        public int getExpectedCEKLength() {
+            return 48;
+        }
+    }
+
+
+    public static class Aes256CbcHmacSha512Provider extends AesCbcHmacShaEncryptionProvider {
+
+        @Override
+        protected int getExpectedAesKeyLength() {
+            return 32;
+        }
+
+        @Override
+        protected String getHmacShaAlgorithm() {
+            return "HMACSHA512";
+        }
+
+        @Override
+        protected int getAuthenticationTagLength() {
+            return 32;
+        }
+
+        @Override
+        public int getExpectedCEKLength() {
+            return 64;
+        }
+    }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/JWEEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/JWEEncryptionProvider.java
new file mode 100644
index 0000000..f9e590c
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/JWEEncryptionProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 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.jose.jwe.enc;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface JWEEncryptionProvider {
+
+    /**
+     * This method usually has 3 outputs:
+     * - generated initialization vector
+     * - encrypted content
+     * - authenticationTag for MAC validation
+     *
+     * It is supposed to call {@link JWE#setEncryptedContentInfo(byte[], byte[], byte[])} after it's finished
+     *
+     * @param jwe
+     * @throws IOException
+     * @throws GeneralSecurityException
+     */
+    void encodeJwe(JWE jwe) throws Exception;
+
+
+    /**
+     * This method is supposed to verify checksums and decrypt content. Then it needs to call {@link JWE#content(byte[])} after it's finished
+     *
+     * @param jwe
+     * @throws IOException
+     * @throws GeneralSecurityException
+     */
+    void verifyAndDecodeJwe(JWE jwe) throws Exception;
+
+
+    /**
+     * This method requires that decoded CEK keys are present in the keyStorage.decodedCEK map before it's called
+     *
+     * @param keyStorage
+     * @return
+     */
+    byte[] serializeCEK(JWEKeyStorage keyStorage);
+
+    /**
+     * This method is supposed to deserialize keys. It requires that {@link JWEKeyStorage#getCekBytes()} is set. After keys are deserialized,
+     * this method needs to call {@link JWEKeyStorage#setCEKKey(Key, JWEKeyStorage.KeyUse)} according to all uses, which this encryption algorithm requires.
+     *
+     * @param keyStorage
+     */
+    void deserializeCEK(JWEKeyStorage keyStorage);
+
+    int getExpectedCEKLength();
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWE.java b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
new file mode 100644
index 0000000..617783e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.BouncyIntegration;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWE {
+
+    static {
+        BouncyIntegration.init();
+    }
+
+    private JWEHeader header;
+    private String base64Header;
+
+    private JWEKeyStorage keyStorage = new JWEKeyStorage();
+    private String base64Cek;
+
+    private byte[] initializationVector;
+
+    private byte[] content;
+    private byte[] encryptedContent;
+
+    private byte[] authenticationTag;
+
+    public JWE header(JWEHeader header) {
+        this.header = header;
+        this.base64Header = null;
+        return this;
+    }
+
+    JWEHeader getHeader() {
+        if (header == null && base64Header != null) {
+            try {
+                byte[] decodedHeader = Base64Url.decode(base64Header);
+                header = JsonSerialization.readValue(decodedHeader, JWEHeader.class);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+        return header;
+    }
+
+    public String getBase64Header() throws IOException {
+        if (base64Header == null && header != null) {
+            byte[] contentBytes = JsonSerialization.writeValueAsBytes(header);
+            base64Header = Base64Url.encode(contentBytes);
+        }
+        return base64Header;
+    }
+
+
+    public JWEKeyStorage getKeyStorage() {
+        return keyStorage;
+    }
+
+
+    public byte[] getInitializationVector() {
+        return initializationVector;
+    }
+
+
+    public JWE content(byte[] content) {
+        this.content = content;
+        return this;
+    }
+
+    public byte[] getContent() {
+        return content;
+    }
+
+    public byte[] getEncryptedContent() {
+        return encryptedContent;
+    }
+
+
+    public byte[] getAuthenticationTag() {
+        return authenticationTag;
+    }
+
+
+    public void setEncryptedContentInfo(byte[] initializationVector, byte[] encryptedContent, byte[] authenticationTag) {
+        this.initializationVector = initializationVector;
+        this.encryptedContent = encryptedContent;
+        this.authenticationTag = authenticationTag;
+    }
+
+
+    public String encodeJwe() throws JWEException {
+        try {
+            if (header == null) {
+                throw new IllegalStateException("Header must be set");
+            }
+            if (content == null) {
+                throw new IllegalStateException("Content must be set");
+            }
+
+            JWEAlgorithmProvider algorithmProvider = JWERegistry.getAlgProvider(header.getAlgorithm());
+            if (algorithmProvider == null) {
+                throw new IllegalArgumentException("No provider for alg '" + header.getAlgorithm() + "'");
+            }
+
+            JWEEncryptionProvider encryptionProvider = JWERegistry.getEncProvider(header.getEncryptionAlgorithm());
+            if (encryptionProvider == null) {
+                throw new IllegalArgumentException("No provider for enc '" + header.getAlgorithm() + "'");
+            }
+
+            keyStorage.setEncryptionProvider(encryptionProvider);
+            keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, true); // Will generate CEK if it's not already present
+
+            byte[] encodedCEK = algorithmProvider.encodeCek(encryptionProvider, keyStorage, keyStorage.getEncryptionKey());
+            base64Cek = Base64Url.encode(encodedCEK);
+
+            encryptionProvider.encodeJwe(this);
+
+            return getEncodedJweString();
+        } catch (Exception e) {
+            throw new JWEException(e);
+        }
+    }
+
+
+    private String getEncodedJweString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(base64Header).append(".")
+                .append(base64Cek).append(".")
+                .append(Base64Url.encode(initializationVector)).append(".")
+                .append(Base64Url.encode(encryptedContent)).append(".")
+                .append(Base64Url.encode(authenticationTag));
+
+        return builder.toString();
+    }
+
+
+    public JWE verifyAndDecodeJwe(String jweStr) throws JWEException {
+        try {
+            String[] parts = jweStr.split("\\.");
+            if (parts.length != 5) {
+                throw new IllegalStateException("Not a JWE String");
+            }
+
+            this.base64Header = parts[0];
+            this.base64Cek = parts[1];
+            this.initializationVector = Base64Url.decode(parts[2]);
+            this.encryptedContent = Base64Url.decode(parts[3]);
+            this.authenticationTag = Base64Url.decode(parts[4]);
+
+            this.header = getHeader();
+            JWEAlgorithmProvider algorithmProvider = JWERegistry.getAlgProvider(header.getAlgorithm());
+            if (algorithmProvider == null) {
+                throw new IllegalArgumentException("No provider for alg '" + header.getAlgorithm() + "'");
+            }
+
+            JWEEncryptionProvider encryptionProvider = JWERegistry.getEncProvider(header.getEncryptionAlgorithm());
+            if (encryptionProvider == null) {
+                throw new IllegalArgumentException("No provider for enc '" + header.getAlgorithm() + "'");
+            }
+
+            keyStorage.setEncryptionProvider(encryptionProvider);
+
+            byte[] decodedCek = algorithmProvider.decodeCek(Base64Url.decode(base64Cek), keyStorage.getEncryptionKey());
+            keyStorage.setCEKBytes(decodedCek);
+
+            encryptionProvider.verifyAndDecodeJwe(this);
+
+            return this;
+        } catch (Exception e) {
+            throw new JWEException(e);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
new file mode 100644
index 0000000..d81141d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWEConstants {
+
+    public static final String DIR = "dir";
+    public static final String A128KW = "A128KW";
+
+    public static final String A128CBC_HS256 = "A128CBC-HS256";
+    public static final String A192CBC_HS384 = "A192CBC-HS384";
+    public static final String A256CBC_HS512  = "A256CBC-HS512";
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEException.java b/core/src/main/java/org/keycloak/jose/jwe/JWEException.java
new file mode 100644
index 0000000..02768ca
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWEException extends Exception {
+
+    public JWEException(String s) {
+        super(s);
+    }
+
+    public JWEException() {
+    }
+
+    public JWEException(Throwable throwable) {
+        super(throwable);
+    }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
new file mode 100644
index 0000000..30b5150
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JWEHeader implements Serializable {
+
+    @JsonProperty("alg")
+    private String algorithm;
+
+    @JsonProperty("enc")
+    private String encryptionAlgorithm;
+
+    @JsonProperty("zip")
+    private String compressionAlgorithm;
+
+
+    @JsonProperty("typ")
+    private String type;
+
+    @JsonProperty("cty")
+    private String contentType;
+
+    @JsonProperty("kid")
+    private String keyId;
+
+    public JWEHeader() {
+    }
+
+    public JWEHeader(String algorithm, String encryptionAlgorithm, String compressionAlgorithm) {
+        this.algorithm = algorithm;
+        this.encryptionAlgorithm = encryptionAlgorithm;
+        this.compressionAlgorithm = compressionAlgorithm;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public String getEncryptionAlgorithm() {
+        return encryptionAlgorithm;
+    }
+
+    public String getCompressionAlgorithm() {
+        return compressionAlgorithm;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public String getKeyId() {
+        return keyId;
+    }
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    static {
+        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+    }
+
+    public String toString() {
+        try {
+            return mapper.writeValueAsString(this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java b/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
new file mode 100644
index 0000000..9ce042c
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+import java.security.Key;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWEKeyStorage {
+
+    private Key encryptionKey;
+
+    private byte[] cekBytes;
+
+    private Map<KeyUse, Key> decodedCEK = new HashMap<>();
+
+    private JWEEncryptionProvider encryptionProvider;
+
+
+    public Key getEncryptionKey() {
+        return encryptionKey;
+    }
+
+    public JWEKeyStorage setEncryptionKey(Key encryptionKey) {
+        this.encryptionKey = encryptionKey;
+        return this;
+    }
+
+
+    public void setCEKBytes(byte[] cekBytes) {
+        this.cekBytes = cekBytes;
+    }
+
+    public byte[] getCekBytes() {
+        if (cekBytes == null) {
+            cekBytes = encryptionProvider.serializeCEK(this);
+        }
+        return cekBytes;
+    }
+
+    public JWEKeyStorage setCEKKey(Key key, KeyUse keyUse) {
+        decodedCEK.put(keyUse, key);
+        return this;
+    }
+
+
+    public Key getCEKKey(KeyUse keyUse, boolean generateIfNotPresent) {
+        Key key = decodedCEK.get(keyUse);
+        if (key == null) {
+            if (encryptionProvider != null) {
+
+                if (cekBytes == null && generateIfNotPresent) {
+                    generateCekBytes();
+                }
+
+                if (cekBytes != null) {
+                    encryptionProvider.deserializeCEK(this);
+                }
+            } else {
+                throw new IllegalStateException("encryptionProvider needs to be set");
+            }
+        }
+
+        return decodedCEK.get(keyUse);
+    }
+
+
+    private void generateCekBytes() {
+        int cekLength = encryptionProvider.getExpectedCEKLength();
+        cekBytes = JWEUtils.generateSecret(cekLength);
+    }
+
+
+    public void setEncryptionProvider(JWEEncryptionProvider encryptionProvider) {
+        this.encryptionProvider = encryptionProvider;
+    }
+
+
+    public enum KeyUse {
+        ENCRYPTION,
+        SIGNATURE
+    }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java b/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
new file mode 100644
index 0000000..80aaea5
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class JWERegistry {
+
+    // https://tools.ietf.org/html/rfc7518#page-12
+    // Registry not pluggable for now. Just supported algorithms included
+    private static final Map<String, JWEEncryptionProvider> ENC_PROVIDERS = new HashMap<>();
+
+    // https://tools.ietf.org/html/rfc7518#page-22
+    // Registry not pluggable for now. Just supported algorithms included
+    private static final Map<String, JWEAlgorithmProvider> ALG_PROVIDERS = new HashMap<>();
+
+
+    static {
+        // Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
+        ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
+        ALG_PROVIDERS.put(JWEConstants.A128KW, new AesKeyWrapAlgorithmProvider());
+
+
+        ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
+        ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());
+        ENC_PROVIDERS.put(JWEConstants.A256CBC_HS512, new AesCbcHmacShaEncryptionProvider.Aes256CbcHmacSha512Provider());
+    }
+
+
+    static JWEAlgorithmProvider getAlgProvider(String alg) {
+        return ALG_PROVIDERS.get(alg);
+    }
+
+
+    static JWEEncryptionProvider getEncProvider(String enc) {
+        return ENC_PROVIDERS.get(enc);
+    }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java b/core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java
new file mode 100644
index 0000000..d88ec56
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 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.jose.jwe;
+
+import java.security.SecureRandom;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWEUtils {
+
+    private JWEUtils() {
+    }
+
+    public static byte[] generateSecret(int bytes) {
+        byte[] buf = new byte[bytes];
+        new SecureRandom().nextBytes(buf);
+        return buf;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jws/AlgorithmType.java b/core/src/main/java/org/keycloak/jose/jws/AlgorithmType.java
index 6c8d93f..236f84c 100755
--- a/core/src/main/java/org/keycloak/jose/jws/AlgorithmType.java
+++ b/core/src/main/java/org/keycloak/jose/jws/AlgorithmType.java
@@ -25,6 +25,7 @@ public enum AlgorithmType {
 
     RSA,
     HMAC,
+    AES,
     ECDSA
 
 }
diff --git a/core/src/main/java/org/keycloak/representations/CodeJWT.java b/core/src/main/java/org/keycloak/representations/CodeJWT.java
new file mode 100644
index 0000000..df43dee
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/CodeJWT.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 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.representations;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CodeJWT extends JsonWebToken {
+
+    @JsonProperty("uss")
+    protected String userSessionId;
+
+    public String getUserSessionId() {
+        return userSessionId;
+    }
+
+    public CodeJWT userSessionId(String userSessionId) {
+        this.userSessionId = userSessionId;
+        return this;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java
index 5226c6b..742983a 100644
--- a/core/src/main/java/org/keycloak/util/TokenUtil.java
+++ b/core/src/main/java/org/keycloak/util/TokenUtil.java
@@ -18,11 +18,18 @@
 package org.keycloak.util;
 
 import org.keycloak.OAuth2Constants;
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.jose.jwe.JWEException;
+import org.keycloak.jose.jwe.JWEHeader;
+import org.keycloak.jose.jwe.JWEKeyStorage;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.JsonWebToken;
 import org.keycloak.representations.RefreshToken;
 
 import java.io.IOException;
+import java.security.Key;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -115,4 +122,52 @@ public class TokenUtil {
         return token.getType().equals(TOKEN_TYPE_OFFLINE);
     }
 
+
+    public static String jweDirectEncode(Key aesKey, Key hmacKey, JsonWebToken jwt) throws JWEException {
+        int keyLength = aesKey.getEncoded().length;
+        String encAlgorithm;
+        switch (keyLength) {
+            case 16: encAlgorithm = JWEConstants.A128CBC_HS256;
+                break;
+            case 24: encAlgorithm = JWEConstants.A192CBC_HS384;
+                break;
+            case 32: encAlgorithm = JWEConstants.A256CBC_HS512;
+                break;
+            default: throw new IllegalArgumentException("Bad size for Encryption key: " + aesKey + ". Valid sizes are 16, 24, 32.");
+        }
+
+        try {
+            byte[] contentBytes = JsonSerialization.writeValueAsBytes(jwt);
+
+            JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, encAlgorithm, null);
+            JWE jwe = new JWE()
+                    .header(jweHeader)
+                    .content(contentBytes);
+
+            jwe.getKeyStorage()
+                    .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+                    .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+            return jwe.encodeJwe();
+        } catch (IOException ioe) {
+            throw new JWEException(ioe);
+        }
+    }
+
+
+    public static <T extends JsonWebToken> T jweDirectVerifyAndDecode(Key aesKey, Key hmacKey, String jweStr, Class<T> expectedClass) throws JWEException {
+        JWE jwe = new JWE();
+        jwe.getKeyStorage()
+                .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+                .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+        jwe.verifyAndDecodeJwe(jweStr);
+
+        try {
+            return JsonSerialization.readValue(jwe.getContent(), expectedClass);
+        } catch (IOException ioe) {
+            throw new JWEException(ioe);
+        }
+    }
+
 }
diff --git a/core/src/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java
new file mode 100644
index 0000000..f97f3c5
--- /dev/null
+++ b/core/src/test/java/org/keycloak/jose/JWETest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2017 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.jose;
+
+import java.io.UnsupportedEncodingException;
+import java.security.Key;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.jose.jwe.JWEException;
+import org.keycloak.jose.jwe.JWEHeader;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWETest {
+
+    private static final String PAYLOAD = "Hello world! How are you man? I hope you are fine. This is some quite a long text, which is much longer than just simple 'Hello World'";
+
+    private static final byte[] HMAC_SHA256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16 };
+    private static final byte[] AES_128_KEY =  new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+
+    private static final byte[] HMAC_SHA512_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+    private static final byte[] AES_256_KEY =  new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+
+    @Test
+    public void testDirect_Aes128CbcHmacSha256() throws Exception {
+        SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+        SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
+
+        testDirectEncryptAndDecrypt(aesKey, hmacKey, JWEConstants.A128CBC_HS256, PAYLOAD, true);
+    }
+
+
+    // Works just on OpenJDK 8. Other JDKs (IBM, Oracle) have restrictions on maximum key size of AES to be 128
+    // @Test
+    public void testDirect_Aes256CbcHmacSha512() throws Exception {
+        final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
+        final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
+
+        testDirectEncryptAndDecrypt(aesKey, hmacKey, JWEConstants.A256CBC_HS512, PAYLOAD, true);
+    }
+
+
+    private void testDirectEncryptAndDecrypt(Key aesKey, Key hmacKey, String encAlgorithm, String payload, boolean sysout) throws Exception {
+        JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, encAlgorithm, null);
+        JWE jwe = new JWE()
+                .header(jweHeader)
+                .content(payload.getBytes("UTF-8"));
+
+        jwe.getKeyStorage()
+                .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+                .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+        String encodedContent = jwe.encodeJwe();
+
+        if (sysout) {
+            System.out.println("Encoded content: " + encodedContent);
+            System.out.println("Encoded content length: " + encodedContent.length());
+        }
+
+        jwe = new JWE();
+        jwe.getKeyStorage()
+                .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+                .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+        jwe.verifyAndDecodeJwe(encodedContent);
+
+        String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+        Assert.assertEquals(payload, decodedContent);
+    }
+
+
+    // @Test
+    public void testPerfDirect() throws Exception {
+        int iterations = 50000;
+
+        long start = System.currentTimeMillis();
+        for (int i=0 ; i<iterations ; i++) {
+            // took around 2950 ms with 50000 iterations
+            SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+            SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
+            String encAlg = JWEConstants.A128CBC_HS256;
+
+            // Similar perf like AES128CBC_HS256
+            //SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
+            //SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
+            //String encAlg = JWEConstants.A256CBC_HS512;
+
+            String payload = PAYLOAD + i;
+            testDirectEncryptAndDecrypt(aesKey, hmacKey, encAlg, payload, false);
+        }
+
+        long took = System.currentTimeMillis() - start;
+        System.out.println("Iterations: " + iterations + ", took: " + took);
+    }
+
+    @Test
+    public void testAesKW_Aes128CbcHmacSha256() throws Exception {
+        SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+
+        JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
+        JWE jwe = new JWE()
+                .header(jweHeader)
+                .content(PAYLOAD.getBytes("UTF-8"));
+
+        jwe.getKeyStorage()
+                .setEncryptionKey(aesKey);
+
+        String encodedContent = jwe.encodeJwe();
+
+        System.out.println("Encoded content: " + encodedContent);
+        System.out.println("Encoded content length: " + encodedContent.length());
+
+        jwe = new JWE();
+        jwe.getKeyStorage()
+                .setEncryptionKey(aesKey);
+
+        jwe.verifyAndDecodeJwe(encodedContent);
+
+        String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+        Assert.assertEquals(PAYLOAD, decodedContent);
+    }
+
+
+    @Test
+    public void externalJweAes128CbcHmacSha256Test() throws UnsupportedEncodingException, JWEException {
+        String externalJwe = "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..qysUrI1iVtiG4Z4jyr7XXg.apdNSQhR7WDMg6IHf5aLVI0gGp6JuOHYmIUtflns4WHmyxOOnh_GShLI6DWaK_SiywTV5gZvZYtl8H8Iv5fTfLkc4tiDDjbdtmsOP7tqyRxVh069gU5UvEAgmCXbIKALutgYXcYe2WM4E6BIHPTSt8jXdkktFcm7XHiD7mpakZyjXsG8p3XVkQJ72WbJI_t6.Ks6gHeko7BRTZ4CFs5ijRA";
+        System.out.println("External encoded content length: " + externalJwe.length());
+
+        final SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+        final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
+
+        JWE jwe = new JWE();
+        jwe.getKeyStorage()
+                .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+                .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+        jwe.verifyAndDecodeJwe(externalJwe);
+
+        String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+        Assert.assertEquals(PAYLOAD, decodedContent);
+    }
+
+
+    // Works just on OpenJDK 8. Other JDKs (IBM, Oracle) have restrictions on maximum key size of AES to be 128
+    // @Test
+    public void externalJweAes256CbcHmacSha512Test() throws UnsupportedEncodingException, JWEException {
+        String externalJwe = "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0..xUPndQ5U69CYaWMKr4nyeg.AzSzba6OdNsvTIoNpub8d2TmYnkY7W8Sd-1S33DjJwJsSaNcfvfXBq5bqXAGVAnLHrLZJKWoEYsmOrYHz3Nao-kpLtUpc4XZI8yiYUqkHTjmxZnfD02R6hz31a5KBCnDTtUEv23VSxm8yUyQKoUTpVHbJ3b2VQvycg2XFUXPsA6oaSSEpz-uwe1Vmun2hUBB.Qal4rMYn1RrXQ9AQ9ONUjUXvlS2ow8np-T8QWMBR0ns";
+        System.out.println("External encoded content length: " + externalJwe.length());
+
+        final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
+        final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
+
+        JWE jwe = new JWE();
+        jwe.getKeyStorage()
+                .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+                .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+        jwe.verifyAndDecodeJwe(externalJwe);
+
+        String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+        Assert.assertEquals(PAYLOAD, decodedContent);
+    }
+
+
+    @Test
+    public void externalJweAes128KeyWrapTest() throws Exception {
+        // See example "A.3" from JWE specification - https://tools.ietf.org/html/rfc7516#page-41
+        String externalJwe = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ";
+
+        byte[] aesKey = Base64Url.decode("GawgguFyGrWKav7AX4VKUg");
+        SecretKeySpec aesKeySpec = new SecretKeySpec(aesKey, "AES");
+
+        JWE jwe = new JWE();
+        jwe.getKeyStorage()
+                .setEncryptionKey(aesKeySpec);
+
+        jwe.verifyAndDecodeJwe(externalJwe);
+
+        String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+        Assert.assertEquals("Live long and prosper.", decodedContent);
+
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
index be086b8..ab8ceed 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
@@ -24,12 +24,10 @@ import java.util.Set;
 
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
 import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
-import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
 import org.keycloak.models.sessions.infinispan.changes.UserSessionClientSessionUpdateTask;
 import org.keycloak.models.sessions.infinispan.changes.UserSessionUpdateTask;
 import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
@@ -40,7 +38,7 @@ import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
  */
 public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
 
-    private final AuthenticatedClientSessionEntity entity;
+    private AuthenticatedClientSessionEntity entity;
     private final ClientModel client;
     private final InfinispanUserSessionProvider provider;
     private final InfinispanChangelogBasedTransaction updateTx;
@@ -63,7 +61,6 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
     @Override
     public void setUserSession(UserSessionModel userSession) {
         String clientUUID = client.getId();
-        UserSessionEntity sessionEntity = this.userSession.getEntity();
 
         // Dettach userSession
         if (userSession == null) {
@@ -83,7 +80,11 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
 
                 @Override
                 public void runUpdate(UserSessionEntity sessionEntity) {
-                    sessionEntity.getAuthenticatedClientSessions().put(clientUUID, entity);
+                    AuthenticatedClientSessionEntity current = sessionEntity.getAuthenticatedClientSessions().putIfAbsent(clientUUID, entity);
+                    if (current != null) {
+                        // It may happen when 2 concurrent HTTP requests trying SSO login against same client
+                        entity = current;
+                    }
                 }
 
             };
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticatedClientSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticatedClientSessionEntity.java
index f67d736..901a313 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticatedClientSessionEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticatedClientSessionEntity.java
@@ -39,7 +39,7 @@ public class AuthenticatedClientSessionEntity implements Serializable {
 
     private String authMethod;
     private String redirectUri;
-    private int timestamp;
+    private volatile int timestamp;
     private String action;
 
     private Set<String> roles;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java
new file mode 100644
index 0000000..9aff14a
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 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.sessions.infinispan;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.infinispan.commons.api.BasicCache;
+import org.keycloak.models.CodeToTokenStoreProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class InfinispanCodeToTokenStoreProvider implements CodeToTokenStoreProvider {
+
+    private final Supplier<BasicCache<UUID, ActionTokenValueEntity>> codeCache;
+    private final KeycloakSession session;
+
+    public InfinispanCodeToTokenStoreProvider(KeycloakSession session, Supplier<BasicCache<UUID, ActionTokenValueEntity>> actionKeyCache) {
+        this.session = session;
+        this.codeCache = actionKeyCache;
+    }
+
+    @Override
+    public boolean putIfAbsent(UUID codeId) {
+        ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(null);
+
+        int lifespanInSeconds = session.getContext().getRealm().getAccessCodeLifespan();
+
+        BasicCache<UUID, ActionTokenValueEntity> cache = codeCache.get();
+        ActionTokenValueEntity existing = cache.putIfAbsent(codeId, tokenValue, lifespanInSeconds, TimeUnit.SECONDS);
+        return existing == null;
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java
new file mode 100644
index 0000000..3fa49f9
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 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.sessions.infinispan;
+
+import java.util.UUID;
+import java.util.function.Supplier;
+
+import org.infinispan.Cache;
+import org.infinispan.client.hotrod.Flag;
+import org.infinispan.client.hotrod.RemoteCache;
+import org.infinispan.commons.api.BasicCache;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.CodeToTokenStoreProvider;
+import org.keycloak.models.CodeToTokenStoreProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
+import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class InfinispanCodeToTokenStoreProviderFactory implements CodeToTokenStoreProviderFactory {
+
+    private static final Logger LOG = Logger.getLogger(InfinispanCodeToTokenStoreProviderFactory.class);
+
+    // Reuse "actionTokens" infinispan cache for now
+    private volatile Supplier<BasicCache<UUID, ActionTokenValueEntity>> codeCache;
+
+    @Override
+    public CodeToTokenStoreProvider create(KeycloakSession session) {
+        lazyInit(session);
+        return new InfinispanCodeToTokenStoreProvider(session, codeCache);
+    }
+
+    private void lazyInit(KeycloakSession session) {
+        if (codeCache == null) {
+            synchronized (this) {
+                if (codeCache == null) {
+                    InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
+                    Cache cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
+
+                    RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
+
+                    if (remoteCache != null) {
+                        LOG.debugf("Having remote stores. Using remote cache '%s' for single-use cache of code", remoteCache.getName());
+                        this.codeCache = () -> {
+                            // Doing this way as flag is per invocation
+                            return remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
+                        };
+                    } else {
+                        LOG.debugf("Not having remote stores. Using normal cache '%s' for single-use cache of code", cache.getName());
+                        this.codeCache = () -> {
+                            return cache;
+                        };
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan";
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java
index 959223c..a86a660 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanKeycloakTransaction.java
@@ -88,6 +88,11 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
                 public void execute() {
                     decorateCache(cache).put(key, value);
                 }
+
+                @Override
+                public String toString() {
+                    return String.format("CacheTaskWithValue: Operation 'put' for key %s", key);
+                }
             });
         }
     }
@@ -104,6 +109,11 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
                 public void execute() {
                     decorateCache(cache).put(key, value, lifespan, lifespanUnit);
                 }
+
+                @Override
+                public String toString() {
+                    return String.format("CacheTaskWithValue: Operation 'put' for key %s, lifespan %d TimeUnit %s", key, lifespan, lifespanUnit.toString());
+                }
             });
         }
     }
@@ -123,6 +133,11 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
                         throw new IllegalStateException("There is already existing value in cache for key " + key);
                     }
                 }
+
+                @Override
+                public String toString() {
+                    return String.format("CacheTaskWithValue: Operation 'putIfAbsent' for key %s", key);
+                }
             });
         }
     }
@@ -142,6 +157,12 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
                 public void execute() {
                     decorateCache(cache).replace(key, value);
                 }
+
+                @Override
+                public String toString() {
+                    return String.format("CacheTaskWithValue: Operation 'replace' for key %s", key);
+                }
+
             });
         }
     }
@@ -162,7 +183,21 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
         log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
 
         Object taskKey = getTaskKey(cache, key);
-        tasks.put(taskKey, () -> decorateCache(cache).remove(key));
+
+        // TODO:performance Eventual performance optimization could be to skip "cache.remove" if item was added in this transaction (EG. authenticationSession valid for single request due to automatic SSO login)
+        tasks.put(taskKey, new CacheTask() {
+
+            @Override
+            public void execute() {
+                decorateCache(cache).remove(key);
+            }
+
+            @Override
+            public String toString() {
+                return String.format("CacheTask: Operation 'remove' for key %s", key);
+            }
+
+        });
     }
 
     // This is for possibility to lookup for session by id, which was created in this transaction
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
index fa9b7db..3f7d258 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
@@ -48,16 +48,16 @@ public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
 
 
     public String useConfigTemplateFromCache() {
-        return useConfigTemplateFromCache==null ? null : useConfigTemplateFromCache.get();
+        return useConfigTemplateFromCache.get();
     }
 
 
     public String remoteServers() {
-        return remoteServers==null ? null : remoteServers.get();
+        return remoteServers.get();
     }
 
 
     public Boolean sessionCache() {
-        return sessionCache==null ? false : sessionCache.get();
+        return sessionCache.get()==null ? false : sessionCache.get();
     }
 }
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.CodeToTokenStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.CodeToTokenStoreProviderFactory
new file mode 100644
index 0000000..e42a1ad
--- /dev/null
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.CodeToTokenStoreProviderFactory
@@ -0,0 +1,18 @@
+#
+# Copyright 2017 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.
+#
+
+org.keycloak.models.sessions.infinispan.InfinispanCodeToTokenStoreProviderFactory
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/models/KeyManager.java b/server-spi/src/main/java/org/keycloak/models/KeyManager.java
index 3916468..bc47dcb 100644
--- a/server-spi/src/main/java/org/keycloak/models/KeyManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeyManager.java
@@ -17,7 +17,7 @@
 
 package org.keycloak.models;
 
-import org.keycloak.keys.HmacKeyMetadata;
+import org.keycloak.keys.SecretKeyMetadata;
 import org.keycloak.keys.RsaKeyMetadata;
 
 import javax.crypto.SecretKey;
@@ -44,7 +44,13 @@ public interface KeyManager {
 
     SecretKey getHmacSecretKey(RealmModel realm, String kid);
 
-    List<HmacKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled);
+    List<SecretKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled);
+
+    ActiveAesKey getActiveAesKey(RealmModel realm);
+
+    SecretKey getAesSecretKey(RealmModel realm, String kid);
+
+    List<SecretKeyMetadata> getAesKeys(RealmModel realm, boolean includeDisabled);
 
     class ActiveRsaKey {
         private final String kid;
@@ -94,4 +100,23 @@ public interface KeyManager {
         }
     }
 
+    class ActiveAesKey {
+        private final String kid;
+        private final SecretKey secretKey;
+
+        public ActiveAesKey(String kid, SecretKey secretKey) {
+            this.kid = kid;
+            this.secretKey = secretKey;
+        }
+
+        public String getKid() {
+            return kid;
+        }
+
+        public SecretKey getSecretKey() {
+            return secretKey;
+        }
+    }
+
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
index 5f913ca..a87309c 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
@@ -56,7 +56,6 @@ public interface CommonClientSessionModel {
 
     public static enum Action {
         OAUTH_GRANT,
-        CODE_TO_TOKEN,
         AUTHENTICATE,
         LOGGED_OUT,
         LOGGING_OUT,
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/AesKeyProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/AesKeyProvider.java
new file mode 100644
index 0000000..4f9fcc2
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/keys/AesKeyProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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.keys;
+
+import org.keycloak.jose.jws.AlgorithmType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface AesKeyProvider extends SecretKeyProvider {
+
+    default AlgorithmType getType() {
+        return AlgorithmType.AES;
+    }
+
+    default String getJavaAlgorithmName() {
+        return "AES";
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/AesKeyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/keys/AesKeyProviderFactory.java
new file mode 100644
index 0000000..4c35963
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/keys/AesKeyProviderFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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.keys;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.keycloak.jose.jws.AlgorithmType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface AesKeyProviderFactory extends KeyProviderFactory<AesKeyProvider> {
+
+    @Override
+    default Map<String, Object> getTypeMetadata() {
+        return Collections.singletonMap("algorithmType", AlgorithmType.AES);
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProvider.java
index 2428774..a525598 100644
--- a/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProvider.java
@@ -19,34 +19,17 @@ package org.keycloak.keys;
 
 import org.keycloak.jose.jws.AlgorithmType;
 
-import javax.crypto.SecretKey;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.X509Certificate;
-import java.util.List;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public interface HmacKeyProvider extends KeyProvider<HmacKeyMetadata> {
+public interface HmacKeyProvider extends SecretKeyProvider {
 
     default AlgorithmType getType() {
         return AlgorithmType.HMAC;
     }
 
-    /**
-     * Return the active secret key, or <code>null</code> if no active key is available.
-     *
-     * @return
-     */
-    SecretKey getSecretKey();
-
-    /**
-     * Return the secret key for the specified kid, or <code>null</code> if the kid is unknown.
-     *
-     * @param kid
-     * @return
-     */
-    SecretKey getSecretKey(String kid);
+    default String getJavaAlgorithmName() {
+        return "HmacSHA256";
+    }
 
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProviderFactory.java
index ba7007b..2e91c4c 100644
--- a/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProviderFactory.java
@@ -25,7 +25,7 @@ import java.util.Map;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public interface HmacKeyProviderFactory extends KeyProviderFactory {
+public interface HmacKeyProviderFactory extends KeyProviderFactory<HmacKeyProvider> {
 
     @Override
     default Map<String, Object> getTypeMetadata() {
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/SecretKeyProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/SecretKeyProvider.java
new file mode 100644
index 0000000..a2b25a1
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/keys/SecretKeyProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 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.keys;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Base for secret key providers (HMAC, AES)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface SecretKeyProvider extends KeyProvider<SecretKeyMetadata> {
+
+    /**
+     * Return the active secret key, or <code>null</code> if no active key is available.
+     *
+     * @return
+     */
+    SecretKey getSecretKey();
+
+    /**
+     * Return the secret key for the specified kid, or <code>null</code> if the kid is unknown.
+     *
+     * @param kid
+     * @return
+     */
+    SecretKey getSecretKey(String kid);
+
+
+    /**
+     * Return name of Java (JCA) algorithm of the key. For example: HmacSHA256
+     * @return
+     */
+    String getJavaAlgorithmName();
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
index 4e3f676..5839aa6 100755
--- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -36,6 +36,7 @@ import org.keycloak.migration.migrators.MigrateTo3_0_0;
 import org.keycloak.migration.migrators.MigrateTo3_1_0;
 import org.keycloak.migration.migrators.MigrateTo3_2_0;
 import org.keycloak.migration.migrators.MigrateTo3_3_0;
+import org.keycloak.migration.migrators.MigrateTo3_4_0;
 import org.keycloak.migration.migrators.Migration;
 import org.keycloak.models.KeycloakSession;
 
@@ -64,7 +65,8 @@ public class MigrationModelManager {
             new MigrateTo3_0_0(),
             new MigrateTo3_1_0(),
             new MigrateTo3_2_0(),
-            new MigrateTo3_3_0()
+            new MigrateTo3_3_0(),
+            new MigrateTo3_4_0()
     };
 
     public static void migrate(KeycloakSession session) {
diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_4_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_4_0.java
new file mode 100644
index 0000000..8c4f930
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_4_0.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 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.migration.migrators;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.utils.DefaultKeyProviders;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MigrateTo3_4_0 implements Migration {
+
+    public static final ModelVersion VERSION = new ModelVersion("3.4.0");
+
+    @Override
+    public void migrate(KeycloakSession session) {
+        session.realms().getRealms().stream().forEach(
+                r -> DefaultKeyProviders.createAesProvider(r)
+        );
+    }
+
+    @Override
+    public ModelVersion getVersion() {
+        return VERSION;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java
index 4e4a8db..ba32eaa 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java
@@ -22,6 +22,10 @@ import java.util.Map;
 
 /**
  * Internal action token store provider.
+ *
+ * It's used for store the details about used action tokens. There is separate provider for OAuth2 codes - {@link CodeToTokenStoreProvider},
+ * which may reuse some components (eg. same infinispan cache)
+ *
  * @author hmlnarik
  */
 public interface ActionTokenStoreProvider extends Provider {
diff --git a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java
new file mode 100644
index 0000000..01b1ada
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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 java.util.UUID;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * Provides single-use cache for OAuth2 code parameter. Used to ensure that particular value of code parameter is used once.
+ *
+ * For now, it is separate provider as it's a bit different use-case than {@link ActionTokenStoreProvider}, however it may reuse some components (eg. same infinispan cache)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface CodeToTokenStoreProvider extends Provider {
+
+    boolean putIfAbsent(UUID codeId);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProviderFactory.java
new file mode 100644
index 0000000..85b2401
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 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.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface CodeToTokenStoreProviderFactory extends ProviderFactory<CodeToTokenStoreProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreSpi.java
new file mode 100644
index 0000000..e71ade6
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreSpi.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 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.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CodeToTokenStoreSpi implements Spi {
+
+    public static final String NAME = "codeToTokenStore";
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return CodeToTokenStoreProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return CodeToTokenStoreProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
index e039292..fbc01ed 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
@@ -41,6 +41,7 @@ public class DefaultKeyProviders {
         realm.addComponentModel(generated);
 
         createSecretProvider(realm);
+        createAesProvider(realm);
     }
 
     public static void createSecretProvider(RealmModel realm) {
@@ -57,6 +58,20 @@ public class DefaultKeyProviders {
         realm.addComponentModel(generated);
     }
 
+    public static void createAesProvider(RealmModel realm) {
+        ComponentModel generated = new ComponentModel();
+        generated.setName("aes-generated");
+        generated.setParentId(realm.getId());
+        generated.setProviderId("aes-generated");
+        generated.setProviderType(KeyProvider.class.getName());
+
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        config.putSingle("priority", "100");
+        generated.setConfig(config);
+
+        realm.addComponentModel(generated);
+    }
+
     public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
         ComponentModel rsa = new ComponentModel();
         rsa.setName("rsa");
@@ -75,6 +90,7 @@ public class DefaultKeyProviders {
         realm.addComponentModel(rsa);
 
         createSecretProvider(realm);
+        createAesProvider(realm);
     }
 
 }
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 543ef25..1413465 100755
--- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -20,6 +20,7 @@ org.keycloak.storage.UserStorageProviderSpi
 org.keycloak.storage.federated.UserFederatedStorageProviderSpi
 org.keycloak.models.RealmSpi
 org.keycloak.models.ActionTokenStoreSpi
+org.keycloak.models.CodeToTokenStoreSpi
 org.keycloak.models.UserSessionSpi
 org.keycloak.models.UserSpi
 org.keycloak.models.session.UserSessionPersisterSpi
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index af7d2f7..e212c92 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -223,7 +223,7 @@ public class AuthenticationProcessor {
     public String generateCode() {
         ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getAuthenticationSession());
         authenticationSession.setTimestamp(Time.currentTime());
-        return accessCode.getCode();
+        return accessCode.getOrGenerateCode();
     }
 
     public EventBuilder newEvent() {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
index b255ace..7910e0f 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
@@ -63,7 +63,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
         List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
         for (IdentityProviderModel identityProvider : identityProviders) {
             if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
-                String accessCode = new ClientSessionCode<>(context.getSession(), context.getRealm(), context.getAuthenticationSession()).getCode();
+                String accessCode = new ClientSessionCode<>(context.getSession(), context.getRealm(), context.getAuthenticationSession()).getOrGenerateCode();
                 String clientId = context.getAuthenticationSession().getClient().getClientId();
                 Response response = Response.seeOther(
                         Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId))
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 3afb34c..3ce9768 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -150,7 +150,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
     public String generateCode() {
         ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession());
         authenticationSession.setTimestamp(Time.currentTime());
-        return accessCode.getCode();
+        return accessCode.getOrGenerateCode();
     }
 
 
diff --git a/services/src/main/java/org/keycloak/keys/Attributes.java b/services/src/main/java/org/keycloak/keys/Attributes.java
index edde62c..8476e55 100644
--- a/services/src/main/java/org/keycloak/keys/Attributes.java
+++ b/services/src/main/java/org/keycloak/keys/Attributes.java
@@ -51,6 +51,8 @@ public interface Attributes {
     String SECRET_KEY = "secret";
 
     String SECRET_SIZE_KEY = "secretSize";
-    ProviderConfigProperty SECRET_SIZE_PROPERTY = new ProviderConfigProperty(SECRET_SIZE_KEY, "Secret size", "Size in bytes for the generated secret", LIST_TYPE, "32", "32", "64", "128", "256", "512");
+    ProviderConfigProperty SECRET_SIZE_PROPERTY = new ProviderConfigProperty(SECRET_SIZE_KEY, "Secret size", "Size in bytes for the generated secret", LIST_TYPE,
+            String.valueOf(GeneratedHmacKeyProviderFactory.DEFAULT_HMAC_KEY_SIZE),
+            "16", "24", "32", "64", "128", "256", "512");
 
 }
diff --git a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
index 4f90f2c..6a33293 100644
--- a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
+++ b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
@@ -83,6 +83,23 @@ public class DefaultKeyManager implements KeyManager {
     }
 
     @Override
+    public ActiveAesKey getActiveAesKey(RealmModel realm) {
+        for (KeyProvider p : getProviders(realm)) {
+            if (p.getType().equals(AlgorithmType.AES)) {
+                AesKeyProvider h = (AesKeyProvider) p;
+                if (h.getKid() != null && h.getSecretKey() != null) {
+                    if (logger.isTraceEnabled()) {
+                        logger.tracev("Active AES Key realm={0} kid={1}", realm.getName(), p.getKid());
+                    }
+                    String kid = p.getKid();
+                    return new ActiveAesKey(kid, h.getSecretKey());
+                }
+            }
+        }
+        throw new RuntimeException("Failed to get keys");
+    }
+
+    @Override
     public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
         if (kid == null) {
             logger.warnv("KID is null, can't find public key", realm.getName(), kid);
@@ -135,7 +152,7 @@ public class DefaultKeyManager implements KeyManager {
     @Override
     public SecretKey getHmacSecretKey(RealmModel realm, String kid) {
         if (kid == null) {
-            logger.warnv("KID is null, can't find public key", realm.getName(), kid);
+            logger.warnv("KID is null, can't find secret key", realm.getName(), kid);
             return null;
         }
 
@@ -158,6 +175,31 @@ public class DefaultKeyManager implements KeyManager {
     }
 
     @Override
+    public SecretKey getAesSecretKey(RealmModel realm, String kid) {
+        if (kid == null) {
+            logger.warnv("KID is null, can't find aes key", realm.getName(), kid);
+            return null;
+        }
+
+        for (KeyProvider p : getProviders(realm)) {
+            if (p.getType().equals(AlgorithmType.AES)) {
+                AesKeyProvider h = (AesKeyProvider) p;
+                SecretKey s = h.getSecretKey(kid);
+                if (s != null) {
+                    if (logger.isTraceEnabled()) {
+                        logger.tracev("Found AES key realm={0} kid={1}", realm.getName(), kid);
+                    }
+                    return s;
+                }
+            }
+        }
+        if (logger.isTraceEnabled()) {
+            logger.tracev("Failed to find AES key realm={0} kid={1}", realm.getName(), kid);
+        }
+        return null;
+    }
+
+    @Override
     public List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled) {
         List<RsaKeyMetadata> keys = new LinkedList<>();
         for (KeyProvider p : getProviders(realm)) {
@@ -174,14 +216,30 @@ public class DefaultKeyManager implements KeyManager {
     }
 
     @Override
-    public List<HmacKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled) {
-        List<HmacKeyMetadata> keys = new LinkedList<>();
+    public List<SecretKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled) {
+        List<SecretKeyMetadata> keys = new LinkedList<>();
         for (KeyProvider p : getProviders(realm)) {
             if (p instanceof HmacKeyProvider) {
                 if (includeDisabled) {
                     keys.addAll(p.getKeyMetadata());
                 } else {
-                    List<HmacKeyMetadata> metadata = p.getKeyMetadata();
+                    List<SecretKeyMetadata> metadata = p.getKeyMetadata();
+                    metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
+                }
+            }
+        }
+        return keys;
+    }
+
+    @Override
+    public List<SecretKeyMetadata> getAesKeys(RealmModel realm, boolean includeDisabled) {
+        List<SecretKeyMetadata> keys = new LinkedList<>();
+        for (KeyProvider p : getProviders(realm)) {
+            if (p instanceof AesKeyProvider) {
+                if (includeDisabled) {
+                    keys.addAll(p.getKeyMetadata());
+                } else {
+                    List<SecretKeyMetadata> metadata = p.getKeyMetadata();
                     metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
                 }
             }
@@ -199,6 +257,7 @@ public class DefaultKeyManager implements KeyManager {
 
             boolean activeRsa = false;
             boolean activeHmac = false;
+            boolean activeAes = false;
 
             for (ComponentModel c : components) {
                 try {
@@ -217,7 +276,13 @@ public class DefaultKeyManager implements KeyManager {
                         if (r.getKid() != null && r.getSecretKey() != null) {
                             activeHmac = true;
                         }
+                    } else if (provider.getType().equals(AlgorithmType.AES)) {
+                        AesKeyProvider r = (AesKeyProvider) provider;
+                        if (r.getKid() != null && r.getSecretKey() != null) {
+                            activeAes = true;
+                        }
                     }
+
                 } catch (Throwable t) {
                     logger.errorv(t, "Failed to load provider {0}", c.getId());
                 }
@@ -231,6 +296,10 @@ public class DefaultKeyManager implements KeyManager {
                 providers.add(new FailsafeHmacKeyProvider());
             }
 
+            if (!activeAes) {
+                providers.add(new FailsafeAesKeyProvider());
+            }
+
             providersMap.put(realm.getId(), providers);
         }
         return providers;
diff --git a/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java
new file mode 100644
index 0000000..d81a596
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/FailsafeAesKeyProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 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.keys;
+
+import org.jboss.logging.Logger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FailsafeAesKeyProvider extends FailsafeSecretKeyProvider implements AesKeyProvider {
+
+    private static final Logger logger = Logger.getLogger(FailsafeAesKeyProvider.class);
+
+    @Override
+    protected Logger logger() {
+        return logger;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java
index 37e837b..676e048 100644
--- a/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/FailsafeHmacKeyProvider.java
@@ -29,61 +29,12 @@ import java.util.List;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class FailsafeHmacKeyProvider implements HmacKeyProvider {
+public class FailsafeHmacKeyProvider extends FailsafeSecretKeyProvider implements HmacKeyProvider {
 
     private static final Logger logger = Logger.getLogger(FailsafeHmacKeyProvider.class);
 
-    private static String KID;
-
-    private static SecretKey KEY;
-
-    private static long EXPIRES;
-
-    private SecretKey key;
-
-    private String kid;
-
-    public FailsafeHmacKeyProvider() {
-        logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
-
-        synchronized (FailsafeHmacKeyProvider.class) {
-            if (EXPIRES < Time.currentTime()) {
-                KEY = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32));
-                KID = KeycloakModelUtils.generateId();
-                EXPIRES = Time.currentTime() + 60 * 10;
-
-                if (EXPIRES > 0) {
-                    logger.warnv("Keys expired, re-generated kid={0}", KID);
-                }
-            }
-
-            kid = KID;
-            key = KEY;
-        }
-    }
-
     @Override
-    public String getKid() {
-        return kid;
+    protected Logger logger() {
+        return logger;
     }
-
-    @Override
-    public SecretKey getSecretKey() {
-        return key;
-    }
-
-    @Override
-    public SecretKey getSecretKey(String kid) {
-        return kid.equals(this.kid) ? key : null;
-    }
-
-    @Override
-    public List<HmacKeyMetadata> getKeyMetadata() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public void close() {
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/keys/FailsafeSecretKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeSecretKeyProvider.java
new file mode 100644
index 0000000..32be263
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/FailsafeSecretKeyProvider.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017 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.keys;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.crypto.SecretKey;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class FailsafeSecretKeyProvider implements SecretKeyProvider {
+
+
+    private static String KID;
+
+    private static SecretKey KEY;
+
+    private static long EXPIRES;
+
+    private SecretKey key;
+
+    private String kid;
+
+    public FailsafeSecretKeyProvider() {
+        logger().errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
+
+        synchronized (FailsafeHmacKeyProvider.class) {
+            if (EXPIRES < Time.currentTime()) {
+                KEY = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32), getJavaAlgorithmName());
+                KID = KeycloakModelUtils.generateId();
+                EXPIRES = Time.currentTime() + 60 * 10;
+
+                if (EXPIRES > 0) {
+                    logger().warnv("Keys expired, re-generated kid={0}", KID);
+                }
+            }
+
+            kid = KID;
+            key = KEY;
+        }
+    }
+
+    @Override
+    public String getKid() {
+        return kid;
+    }
+
+    @Override
+    public SecretKey getSecretKey() {
+        return key;
+    }
+
+    @Override
+    public SecretKey getSecretKey(String kid) {
+        return kid.equals(this.kid) ? key : null;
+    }
+
+    @Override
+    public List<SecretKeyMetadata> getKeyMetadata() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void close() {
+    }
+
+    protected abstract Logger logger();
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedAesKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedAesKeyProvider.java
new file mode 100644
index 0000000..cb07323
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedAesKeyProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 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.keys;
+
+import org.keycloak.component.ComponentModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class GeneratedAesKeyProvider extends GeneratedSecretKeyProvider implements AesKeyProvider {
+
+    public GeneratedAesKeyProvider(ComponentModel model) {
+        super(model);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedAesKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedAesKeyProviderFactory.java
new file mode 100644
index 0000000..b9f5a06
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedAesKeyProviderFactory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 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.keys;
+
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFactory<AesKeyProvider> implements AesKeyProviderFactory {
+
+    private static final Logger logger = Logger.getLogger(GeneratedAesKeyProviderFactory.class);
+
+    public static final String ID = "aes-generated";
+
+    private static final String HELP_TEXT = "Generates AES secret key";
+
+    private static final ProviderConfigProperty AES_KEY_SIZE_PROPERTY;
+
+    private static final int DEFAULT_AES_KEY_SIZE = 16;
+
+    static {
+        AES_KEY_SIZE_PROPERTY = new ProviderConfigProperty(Attributes.SECRET_SIZE_KEY, "AES Key size",
+                "Size in bytes for the generated AES Key. Size 16 is for AES-128, Size 24 for AES-192 and Size 32 for AES-256. WARN: Bigger keys then 128 bits are not allowed on some JDK implementations",
+                LIST_TYPE, String.valueOf(DEFAULT_AES_KEY_SIZE), "16", "24", "32");
+    }
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = SecretKeyProviderUtils.configurationBuilder()
+            .property(AES_KEY_SIZE_PROPERTY)
+            .build();
+
+    @Override
+    public AesKeyProvider create(KeycloakSession session, ComponentModel model) {
+        return new GeneratedAesKeyProvider(model);
+    }
+
+    @Override
+    public String getHelpText() {
+        return HELP_TEXT;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return CONFIG_PROPERTIES;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    protected Logger logger() {
+        return logger;
+    }
+
+    @Override
+    protected int getDefaultKeySize() {
+        return DEFAULT_AES_KEY_SIZE;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java
index a989ac3..dfc8fb2 100644
--- a/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProvider.java
@@ -17,87 +17,16 @@
 
 package org.keycloak.keys;
 
-import org.keycloak.common.util.Base64Url;
-import org.keycloak.common.util.KeyUtils;
 import org.keycloak.component.ComponentModel;
-import org.keycloak.jose.jws.AlgorithmType;
 
-import javax.crypto.SecretKey;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class GeneratedHmacKeyProvider implements HmacKeyProvider {
-
-    private final boolean enabled;
-
-    private final boolean active;
-
-    private final ComponentModel model;
-    private final String kid;
-    private final SecretKey secretKey;
+public class GeneratedHmacKeyProvider extends GeneratedSecretKeyProvider implements HmacKeyProvider {
 
     public GeneratedHmacKeyProvider(ComponentModel model) {
-        this.enabled = model.get(Attributes.ENABLED_KEY, true);
-        this.active = model.get(Attributes.ACTIVE_KEY, true);
-        this.kid = model.get(Attributes.KID_KEY);
-        this.model = model;
-
-        if (model.hasNote(SecretKey.class.getName())) {
-            secretKey = model.getNote(SecretKey.class.getName());
-        } else {
-            secretKey = KeyUtils.loadSecretKey(Base64Url.decode(model.get(Attributes.SECRET_KEY)));
-            model.setNote(SecretKey.class.getName(), secretKey);
-        }
-    }
-
-    @Override
-    public SecretKey getSecretKey() {
-        return isActive() ? secretKey : null;
-    }
-
-    @Override
-    public SecretKey getSecretKey(String kid) {
-        return isEnabled() && kid.equals(this.kid) ? secretKey : null;
-    }
-
-    @Override
-    public String getKid() {
-        return isActive() ? kid : null;
-    }
-
-    @Override
-    public List<HmacKeyMetadata> getKeyMetadata() {
-        if (kid != null && secretKey != null) {
-            HmacKeyMetadata k = new HmacKeyMetadata();
-            k.setProviderId(model.getId());
-            k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
-            k.setKid(kid);
-            if (isActive()) {
-                k.setStatus(KeyMetadata.Status.ACTIVE);
-            } else if (isEnabled()) {
-                k.setStatus(KeyMetadata.Status.PASSIVE);
-            } else {
-                k.setStatus(KeyMetadata.Status.DISABLED);
-            }
-            return Collections.singletonList(k);
-        } else {
-            return Collections.emptyList();
-        }
-    }
-
-    @Override
-    public void close() {
-    }
-
-    private boolean isEnabled() {
-        return secretKey != null && enabled;
-    }
-
-    private boolean isActive() {
-        return isEnabled() && active;
+        super(model);
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProviderFactory.java
index 3a72517..ab4902a 100644
--- a/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProviderFactory.java
+++ b/services/src/main/java/org/keycloak/keys/GeneratedHmacKeyProviderFactory.java
@@ -34,7 +34,7 @@ import java.util.List;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class GeneratedHmacKeyProviderFactory extends AbstractHmacKeyProviderFactory {
+public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderFactory<HmacKeyProvider> implements HmacKeyProviderFactory {
 
     private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
 
@@ -42,12 +42,14 @@ public class GeneratedHmacKeyProviderFactory extends AbstractHmacKeyProviderFact
 
     private static final String HELP_TEXT = "Generates HMAC secret key";
 
-    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractHmacKeyProviderFactory.configurationBuilder()
+    public static final int DEFAULT_HMAC_KEY_SIZE = 32;
+
+    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = SecretKeyProviderUtils.configurationBuilder()
             .property(Attributes.SECRET_SIZE_PROPERTY)
             .build();
 
     @Override
-    public KeyProvider create(KeycloakSession session, ComponentModel model) {
+    public HmacKeyProvider create(KeycloakSession session, ComponentModel model) {
         return new GeneratedHmacKeyProvider(model);
     }
 
@@ -62,50 +64,17 @@ public class GeneratedHmacKeyProviderFactory extends AbstractHmacKeyProviderFact
     }
 
     @Override
-    public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
-        ConfigurationValidationHelper.check(model).checkList(Attributes.SECRET_SIZE_PROPERTY, false);
-
-        int size = model.get(Attributes.SECRET_SIZE_KEY, 32);
-
-        if (!(model.contains(Attributes.SECRET_KEY))) {
-            generateSecret(model, size);
-            logger.debugv("Generated secret for {0}", realm.getName());
-        } else {
-            int currentSize = Base64Url.decode(model.get(Attributes.SECRET_KEY)).length;
-            if (currentSize != size) {
-                generateSecret(model, size);
-                logger.debugv("Secret size changed, generating new secret for {0}", realm.getName());
-            }
-        }
-    }
-
-    private void generateSecret(ComponentModel model, int size) {
-        try {
-            byte[] secret = KeycloakModelUtils.generateSecret(size);
-            model.put(Attributes.SECRET_KEY, Base64Url.encode(secret));
-
-            String kid = KeycloakModelUtils.generateId();
-            model.put(Attributes.KID_KEY, kid);
-        } catch (Throwable t) {
-            throw new ComponentValidationException("Failed to generate secret", t);
-        }
-    }
-
-    @Override
-    public void init(Config.Scope config) {
-    }
-
-    @Override
-    public void postInit(KeycloakSessionFactory factory) {
+    public String getId() {
+        return ID;
     }
 
     @Override
-    public void close() {
+    protected Logger logger() {
+        return logger;
     }
 
     @Override
-    public String getId() {
-        return ID;
+    protected int getDefaultKeySize() {
+        return DEFAULT_HMAC_KEY_SIZE;
     }
-
 }
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedSecretKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedSecretKeyProvider.java
new file mode 100644
index 0000000..76b7ea8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedSecretKeyProvider.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 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.keys;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.crypto.SecretKey;
+
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.component.ComponentModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class GeneratedSecretKeyProvider implements SecretKeyProvider {
+
+    private final boolean enabled;
+
+    private final boolean active;
+
+    private final ComponentModel model;
+    private final String kid;
+    private final SecretKey secretKey;
+
+    public GeneratedSecretKeyProvider(ComponentModel model) {
+        this.enabled = model.get(Attributes.ENABLED_KEY, true);
+        this.active = model.get(Attributes.ACTIVE_KEY, true);
+        this.kid = model.get(Attributes.KID_KEY);
+        this.model = model;
+
+        if (model.hasNote(SecretKey.class.getName())) {
+            secretKey = model.getNote(SecretKey.class.getName());
+        } else {
+            secretKey = KeyUtils.loadSecretKey(Base64Url.decode(model.get(Attributes.SECRET_KEY)), getJavaAlgorithmName());
+            model.setNote(SecretKey.class.getName(), secretKey);
+        }
+    }
+
+    @Override
+    public SecretKey getSecretKey() {
+        return isActive() ? secretKey : null;
+    }
+
+    @Override
+    public SecretKey getSecretKey(String kid) {
+        return isEnabled() && kid.equals(this.kid) ? secretKey : null;
+    }
+
+    @Override
+    public String getKid() {
+        return isActive() ? kid : null;
+    }
+
+    @Override
+    public List<SecretKeyMetadata> getKeyMetadata() {
+        if (kid != null && secretKey != null) {
+            SecretKeyMetadata k = new SecretKeyMetadata();
+            k.setProviderId(model.getId());
+            k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
+            k.setKid(kid);
+            if (isActive()) {
+                k.setStatus(KeyMetadata.Status.ACTIVE);
+            } else if (isEnabled()) {
+                k.setStatus(KeyMetadata.Status.PASSIVE);
+            } else {
+                k.setStatus(KeyMetadata.Status.DISABLED);
+            }
+            return Collections.singletonList(k);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public void close() {
+    }
+
+    private boolean isEnabled() {
+        return secretKey != null && enabled;
+    }
+
+    private boolean isActive() {
+        return isEnabled() && active;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedSecretKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedSecretKeyProviderFactory.java
new file mode 100644
index 0000000..3211cfc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedSecretKeyProviderFactory.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017 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.keys;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.provider.ConfigurationValidationHelper;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class GeneratedSecretKeyProviderFactory<T extends KeyProvider> implements KeyProviderFactory<T> {
+
+    @Override
+    public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
+        ConfigurationValidationHelper validation = SecretKeyProviderUtils.validateConfiguration(model);
+        validation.checkList(Attributes.SECRET_SIZE_PROPERTY, false);
+
+        int size = model.get(Attributes.SECRET_SIZE_KEY, getDefaultKeySize());
+
+        if (!(model.contains(Attributes.SECRET_KEY))) {
+            generateSecret(model, size);
+            logger().debugv("Generated secret for {0}", realm.getName());
+        } else {
+            int currentSize = Base64Url.decode(model.get(Attributes.SECRET_KEY)).length;
+            if (currentSize != size) {
+                generateSecret(model, size);
+                logger().debugv("Secret size changed, generating new secret for {0}", realm.getName());
+            }
+        }
+    }
+
+    private void generateSecret(ComponentModel model, int size) {
+        try {
+            byte[] secret = KeycloakModelUtils.generateSecret(size);
+            model.put(Attributes.SECRET_KEY, Base64Url.encode(secret));
+
+            String kid = KeycloakModelUtils.generateId();
+            model.put(Attributes.KID_KEY, kid);
+        } catch (Throwable t) {
+            throw new ComponentValidationException("Failed to generate secret", t);
+        }
+    }
+
+    protected abstract Logger logger();
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    protected abstract int getDefaultKeySize();
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index a28f283..42b42d9 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -245,35 +245,27 @@ public class TokenEndpoint {
             throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
         }
 
-        String[] parts = code.split("\\.");
-        if (parts.length == 4) {
-            event.detail(Details.CODE_ID, parts[2]);
-        }
-
-        ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, AuthenticatedClientSessionModel.class);
+        ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, event, AuthenticatedClientSessionModel.class);
         if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
-            event.error(Errors.INVALID_CODE);
+            AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
 
             // Attempt to use same code twice should invalidate existing clientSession
-            AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
             if (clientSession != null) {
                 clientSession.setUserSession(null);
             }
 
+            event.error(Errors.INVALID_CODE);
+
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not valid", Response.Status.BAD_REQUEST);
         }
 
         AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
 
-        if (!parseResult.getCode().isValid(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
-            event.error(Errors.INVALID_CODE);
+        if (parseResult.isExpiredToken()) {
+            event.error(Errors.EXPIRED_CODE);
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
         }
 
-        // TODO: This shouldn't be needed to write into the AuthenticatedClientSessionModel itself
-        parseResult.getCode().setAction(null);
-
-        // TODO: Maybe rather create userSession even at this stage?
         UserSessionModel userSession = clientSession.getUserSession();
 
         if (userSession == null) {
@@ -281,20 +273,20 @@ public class TokenEndpoint {
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User session not found", Response.Status.BAD_REQUEST);
         }
 
+
         UserModel user = userSession.getUser();
         if (user == null) {
             event.error(Errors.USER_NOT_FOUND);
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User not found", Response.Status.BAD_REQUEST);
         }
+
+        event.user(userSession.getUser());
+
         if (!user.isEnabled()) {
             event.error(Errors.USER_DISABLED);
             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User disabled", Response.Status.BAD_REQUEST);
         }
 
-        event.user(userSession.getUser());
-
-        event.session(userSession.getId());
-
         String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM);
         String formParam = formParams.getFirst(OAuth2Constants.REDIRECT_URI);
         if (redirectUri != null && !redirectUri.equals(formParam)) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 13d24a7..91405d3 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -29,7 +29,6 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
 import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
 import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@@ -39,7 +38,6 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationSessionManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.ResourceAdminManager;
-import org.keycloak.sessions.CommonClientSessionModel;
 import org.keycloak.sessions.AuthenticationSessionModel;
 import org.keycloak.util.TokenUtil;
 
@@ -185,9 +183,10 @@ public class OIDCLoginProtocol implements LoginProtocol {
             redirectUri.addParam(OAuth2Constants.STATE, state);
 
         // Standard or hybrid flow
+        String code = null;
         if (responseType.hasResponseType(OIDCResponseType.CODE)) {
-            accessCode.setAction(CommonClientSessionModel.Action.CODE_TO_TOKEN.name());
-            redirectUri.addParam(OAuth2Constants.CODE, accessCode.getCode());
+            code = accessCode.getOrGenerateCode();
+            redirectUri.addParam(OAuth2Constants.CODE, code);
         }
 
         // Implicit or hybrid flow
@@ -205,7 +204,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
                 }
 
                 if (responseType.hasResponseType(OIDCResponseType.CODE)) {
-                    responseBuilder.generateCodeHash(accessCode.getCode());
+                    responseBuilder.generateCodeHash(code);
                 }
 
             }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 4f6f4ec..19c188a 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -849,7 +849,7 @@ public class AuthenticationManager {
 
                 return session.getProvider(LoginFormsProvider.class)
                         .setExecution(execution)
-                        .setClientSessionCode(accessCode.getCode())
+                        .setClientSessionCode(accessCode.getOrGenerateCode())
                         .setAccessRequest(realmRoles, resourceRoles, protocolMappers)
                         .createOAuthGrant();
             } else {
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index 59158e6..503973e 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -17,19 +17,16 @@
 
 package org.keycloak.services.managers;
 
-import org.jboss.logging.Logger;
-import org.keycloak.common.util.Base64Url;
 import org.keycloak.common.util.Time;
+import org.keycloak.events.EventBuilder;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.sessions.CommonClientSessionModel;
 
-import java.security.MessageDigest;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -39,10 +36,6 @@ import java.util.Set;
  */
 public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> {
 
-    private static final String ACTIVE_CODE = "active_code";
-
-    private static final Logger logger = Logger.getLogger(ClientSessionCode.class);
-
     private KeycloakSession session;
     private final RealmModel realm;
     private final CLIENT_SESSION commonLoginSession;
@@ -63,6 +56,7 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> 
         ClientSessionCode<CLIENT_SESSION> code;
         boolean authSessionNotFound;
         boolean illegalHash;
+        boolean expiredToken;
         CLIENT_SESSION clientSession;
 
         public ClientSessionCode<CLIENT_SESSION> getCode() {
@@ -77,29 +71,39 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> 
             return illegalHash;
         }
 
+        public boolean isExpiredToken() {
+            return expiredToken;
+        }
+
         public CLIENT_SESSION getClientSession() {
             return clientSession;
         }
     }
 
-    public static <CLIENT_SESSION extends CommonClientSessionModel> ParseResult<CLIENT_SESSION> parseResult(String code, KeycloakSession session, RealmModel realm, Class<CLIENT_SESSION> sessionClass) {
+    public static <CLIENT_SESSION extends CommonClientSessionModel> ParseResult<CLIENT_SESSION> parseResult(String code, KeycloakSession session, RealmModel realm, EventBuilder event, Class<CLIENT_SESSION> sessionClass) {
         ParseResult<CLIENT_SESSION> result = new ParseResult<>();
         if (code == null) {
             result.illegalHash = true;
             return result;
         }
         try {
-            result.clientSession = getClientSession(code, session, realm, sessionClass);
+            CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser = CodeGenerateUtil.getParser(sessionClass);
+            result.clientSession = getClientSession(code, session, realm, event, clientSessionParser);
             if (result.clientSession == null) {
                 result.authSessionNotFound = true;
                 return result;
             }
 
-            if (!verifyCode(code, result.clientSession)) {
+            if (!clientSessionParser.verifyCode(session, code, result.clientSession)) {
                 result.illegalHash = true;
                 return result;
             }
 
+            if (clientSessionParser.isExpired(session, code, result.clientSession)) {
+                result.expiredToken = true;
+                return result;
+            }
+
             result.code = new ClientSessionCode<CLIENT_SESSION>(session, realm, result.clientSession);
             return result;
         } catch (RuntimeException e) {
@@ -108,13 +112,19 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> 
         }
     }
 
-    public static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, Class<CLIENT_SESSION> sessionClass) {
-        CommonClientSessionModel clientSessionn = CodeGenerateUtil.getParser(sessionClass).parseSession(code, session, realm);;
-        CLIENT_SESSION clientSession = sessionClass.cast(clientSessionn);
 
-        return clientSession;
+    public static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event, Class<CLIENT_SESSION> sessionClass) {
+        CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser = CodeGenerateUtil.getParser(sessionClass);
+        return getClientSession(code, session, realm, event, clientSessionParser);
     }
 
+
+    private static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event,
+                                                                                                     CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser) {
+        return clientSessionParser.parseSession(code, session, realm, event);
+    }
+
+
     public CLIENT_SESSION getClientSession() {
         return commonLoginSession;
     }
@@ -203,52 +213,9 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel> 
         commonLoginSession.setTimestamp(Time.currentTime());
     }
 
-    public String getCode() {
+    public String getOrGenerateCode() {
         CodeGenerateUtil.ClientSessionParser parser = CodeGenerateUtil.getParser(commonLoginSession.getClass());
-        String nextCode = parser.getNote(commonLoginSession, ACTIVE_CODE);
-        if (nextCode == null) {
-            nextCode = generateCode(commonLoginSession);
-        } else {
-            logger.debug("Code already generated for session, using same code");
-        }
-        return nextCode;
-    }
-
-    private static String generateCode(CommonClientSessionModel authSession) {
-        try {
-            String actionId = Base64Url.encode(KeycloakModelUtils.generateSecret());
-
-            StringBuilder sb = new StringBuilder();
-            sb.append(actionId);
-            sb.append('.');
-            sb.append(authSession.getId());
-
-            CodeGenerateUtil.ClientSessionParser parser = CodeGenerateUtil.getParser(authSession.getClass());
-
-            String code = parser.generateCode(authSession, actionId);
-            parser.setNote(authSession, ACTIVE_CODE, code);
-
-            return code;
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        return parser.retrieveCode(session, commonLoginSession);
     }
 
-    public static boolean verifyCode(String code, CommonClientSessionModel authSession) {
-        try {
-            CodeGenerateUtil.ClientSessionParser parser = CodeGenerateUtil.getParser(authSession.getClass());
-
-            String activeCode = parser.getNote(authSession, ACTIVE_CODE);
-            if (activeCode == null) {
-                logger.debug("Active code not found in client session");
-                return false;
-            }
-
-            parser.removeNote(authSession, ACTIVE_CODE);
-
-            return MessageDigest.isEqual(code.getBytes(), activeCode.getBytes());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
index 3d0c9ca..70e3f35 100644
--- a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
+++ b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
@@ -17,16 +17,30 @@
 
 package org.keycloak.services.managers;
 
+import java.security.MessageDigest;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+import javax.crypto.SecretKey;
 
 import org.jboss.logging.Logger;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.Time;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.jose.jwe.JWEException;
 import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.CodeToTokenStoreProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.CodeJWT;
 import org.keycloak.sessions.CommonClientSessionModel;
 import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.util.TokenUtil;
 
 /**
  *
@@ -36,11 +50,18 @@ class CodeGenerateUtil {
 
     private static final Logger logger = Logger.getLogger(CodeGenerateUtil.class);
 
-    private static final Map<Class<? extends CommonClientSessionModel>, ClientSessionParser> PARSERS = new HashMap<>();
+    private static final String ACTIVE_CODE = "active_code";
+
+    private static final Map<Class<? extends CommonClientSessionModel>, Supplier<ClientSessionParser>> PARSERS = new HashMap<>();
 
     static {
-        PARSERS.put(AuthenticationSessionModel.class, new AuthenticationSessionModelParser());
-        PARSERS.put(AuthenticatedClientSessionModel.class, new AuthenticatedClientSessionModelParser());
+        PARSERS.put(AuthenticationSessionModel.class, () -> {
+            return new AuthenticationSessionModelParser();
+        });
+
+        PARSERS.put(AuthenticatedClientSessionModel.class, () -> {
+            return new AuthenticatedClientSessionModelParser();
+        });
     }
 
 
@@ -48,7 +69,7 @@ class CodeGenerateUtil {
     static <CS extends CommonClientSessionModel> ClientSessionParser<CS> getParser(Class<CS> clientSessionClass) {
         for (Class<?> c : PARSERS.keySet()) {
             if (c.isAssignableFrom(clientSessionClass)) {
-                return PARSERS.get(c);
+                return PARSERS.get(c).get();
             }
         }
         return null;
@@ -57,17 +78,15 @@ class CodeGenerateUtil {
 
     interface ClientSessionParser<CS extends CommonClientSessionModel> {
 
-        CS parseSession(String code, KeycloakSession session, RealmModel realm);
+        CS parseSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event);
 
-        String generateCode(CS clientSession, String actionId);
+        String retrieveCode(KeycloakSession session, CS clientSession);
 
         void removeExpiredSession(KeycloakSession session, CS clientSession);
 
-        String getNote(CS clientSession, String name);
+        boolean verifyCode(KeycloakSession session, String code, CS clientSession);
 
-        void removeNote(CS clientSession, String name);
-
-        void setNote(CS clientSession, String name, String value);
+        boolean isExpired(KeycloakSession session, String code, CS clientSession);
 
     }
 
@@ -78,95 +97,149 @@ class CodeGenerateUtil {
     private static class AuthenticationSessionModelParser implements ClientSessionParser<AuthenticationSessionModel> {
 
         @Override
-        public AuthenticationSessionModel parseSession(String code, KeycloakSession session, RealmModel realm) {
+        public AuthenticationSessionModel parseSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event) {
             // Read authSessionID from cookie. Code is ignored for now
             return new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
         }
 
         @Override
-        public String generateCode(AuthenticationSessionModel clientSession, String actionId) {
-            return actionId;
+        public String retrieveCode(KeycloakSession session, AuthenticationSessionModel authSession) {
+            String nextCode = authSession.getAuthNote(ACTIVE_CODE);
+            if (nextCode == null) {
+                String actionId = Base64Url.encode(KeycloakModelUtils.generateSecret());
+                authSession.setAuthNote(ACTIVE_CODE, actionId);
+                nextCode = actionId;
+            } else {
+                logger.debug("Code already generated for authentication session, using same code");
+            }
+
+            return nextCode;
         }
 
+
         @Override
         public void removeExpiredSession(KeycloakSession session, AuthenticationSessionModel clientSession) {
             new AuthenticationSessionManager(session).removeAuthenticationSession(clientSession.getRealm(), clientSession, true);
         }
 
-        @Override
-        public String getNote(AuthenticationSessionModel clientSession, String name) {
-            return clientSession.getAuthNote(name);
-        }
 
         @Override
-        public void removeNote(AuthenticationSessionModel clientSession, String name) {
-            clientSession.removeAuthNote(name);
+        public boolean verifyCode(KeycloakSession session, String code, AuthenticationSessionModel authSession) {
+            String activeCode = authSession.getAuthNote(ACTIVE_CODE);
+            if (activeCode == null) {
+                logger.debug("Active code not found in authentication session");
+                return false;
+            }
+
+            authSession.removeAuthNote(ACTIVE_CODE);
+
+            return MessageDigest.isEqual(code.getBytes(), activeCode.getBytes());
         }
 
+
         @Override
-        public void setNote(AuthenticationSessionModel clientSession, String name, String value) {
-            clientSession.setAuthNote(name, value);
+        public boolean isExpired(KeycloakSession session, String code, AuthenticationSessionModel clientSession) {
+            return false;
         }
     }
 
 
     private static class AuthenticatedClientSessionModelParser implements ClientSessionParser<AuthenticatedClientSessionModel> {
 
+        private CodeJWT codeJWT;
+
         @Override
-        public AuthenticatedClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm) {
+        public AuthenticatedClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event) {
+            SecretKey aesKey = session.keys().getActiveAesKey(realm).getSecretKey();
+            SecretKey hmacKey = session.keys().getActiveHmacKey(realm).getSecretKey();
+
             try {
-                String[] parts = code.split("\\.");
-                String userSessionId = parts[2];
-                String clientUUID = parts[3];
+                codeJWT = TokenUtil.jweDirectVerifyAndDecode(aesKey, hmacKey, code, CodeJWT.class);
+            } catch (JWEException jweException) {
+                logger.error("Exception during JWE Verification or decode", jweException);
+                return null;
+            }
+
+            event.detail(Details.CODE_ID, codeJWT.getUserSessionId());
+            event.session(codeJWT.getUserSessionId());
 
-                UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClientAndCodeToTokenAction(realm, userSessionId, clientUUID);
+            UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, codeJWT.getUserSessionId(), codeJWT.getIssuedFor());
+            if (userSession == null) {
+                // TODO:mposolda Temporary workaround needed to track if code is invalid or was already used. Will be good to remove once used OAuth codes are tracked through one-time cache
+                userSession = session.sessions().getUserSession(realm, codeJWT.getUserSessionId());
                 if (userSession == null) {
-                    // TODO:mposolda Temporary workaround needed to track if code is invalid or was already used. Will be good to remove once used OAuth codes are tracked through one-time cache
-                    userSession = session.sessions().getUserSession(realm, userSessionId);
-                    if (userSession == null) {
-                        return null;
-                    }
+                    return null;
                 }
-
-                return userSession.getAuthenticatedClientSessions().get(clientUUID);
-            } catch (ArrayIndexOutOfBoundsException e) {
-                return null;
             }
-        }
 
-        @Override
-        public String generateCode(AuthenticatedClientSessionModel clientSession, String actionId) {
-            String userSessionId = clientSession.getUserSession().getId();
-            String clientUUID = clientSession.getClient().getId();
-            StringBuilder sb = new StringBuilder();
-            sb.append("uss.");
-            sb.append(actionId);
-            sb.append('.');
-            sb.append(userSessionId);
-            sb.append('.');
-            sb.append(clientUUID);
-
-            return sb.toString();
+            return userSession.getAuthenticatedClientSessions().get(codeJWT.getIssuedFor());
+
         }
 
+
         @Override
-        public void removeExpiredSession(KeycloakSession session, AuthenticatedClientSessionModel clientSession) {
-            throw new IllegalStateException("Not yet implemented");
+        public String retrieveCode(KeycloakSession session, AuthenticatedClientSessionModel clientSession) {
+            String actionId = KeycloakModelUtils.generateId();
+
+            CodeJWT codeJWT = new CodeJWT();
+            codeJWT.id(actionId);
+            codeJWT.issuedFor(clientSession.getClient().getId());
+            codeJWT.userSessionId(clientSession.getUserSession().getId());
+
+            RealmModel realm = clientSession.getRealm();
+
+            int issuedAt = Time.currentTime();
+            codeJWT.issuedAt(issuedAt);
+            codeJWT.expiration(issuedAt + realm.getAccessCodeLifespan());
+
+            SecretKey aesKey = session.keys().getActiveAesKey(realm).getSecretKey();
+            SecretKey hmacKey = session.keys().getActiveHmacKey(realm).getSecretKey();
+
+            if (logger.isTraceEnabled()) {
+                logger.tracef("Using AES key of length '%d' bytes and HMAC key of length '%d' bytes . Client: '%s', User Session: '%s'", aesKey.getEncoded().length,
+                        hmacKey.getEncoded().length, clientSession.getClient().getClientId(), clientSession.getUserSession().getId());
+            }
+
+            try {
+                return TokenUtil.jweDirectEncode(aesKey, hmacKey, codeJWT);
+            } catch (JWEException jweEx) {
+                throw new RuntimeException(jweEx);
+            }
         }
 
+
         @Override
-        public String getNote(AuthenticatedClientSessionModel clientSession, String name) {
-            return clientSession.getNote(name);
+        public boolean verifyCode(KeycloakSession session, String code, AuthenticatedClientSessionModel clientSession) {
+            if (codeJWT == null) {
+                throw new IllegalStateException("Illegal use. codeJWT not yet set");
+            }
+
+            UUID codeId = UUID.fromString(codeJWT.getId());
+            CodeToTokenStoreProvider singleUseCache = session.getProvider(CodeToTokenStoreProvider.class);
+
+            if (singleUseCache.putIfAbsent(codeId)) {
+
+                if (logger.isTraceEnabled()) {
+                    logger.tracef("Added code '%s' to single-use cache. User session: %s, client: %s", codeJWT.getId(), codeJWT.getUserSessionId(), codeJWT.getIssuedFor());
+                }
+
+                return true;
+            } else {
+                logger.warnf("Code '%s' already used for userSession '%s' and client '%s'.", codeJWT.getId(), codeJWT.getUserSessionId(), codeJWT.getIssuedFor());
+                return false;
+            }
         }
 
+
         @Override
-        public void removeNote(AuthenticatedClientSessionModel clientSession, String name) {
-            clientSession.removeNote(name);
+        public void removeExpiredSession(KeycloakSession session, AuthenticatedClientSessionModel clientSession) {
+            throw new IllegalStateException("Not yet implemented");
         }
 
+
         @Override
-        public void setNote(AuthenticatedClientSessionModel clientSession, String name, String value) {
-            clientSession.setNote(name, value);
+        public boolean isExpired(KeycloakSession session, String code, AuthenticatedClientSessionModel clientSession) {
+            return !codeJWT.isActive();
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java
index 11795e5..de2516f 100644
--- a/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java
@@ -46,19 +46,14 @@ public class UserSessionCrossDCManager {
     }
 
 
-    // get userSession if it has "authenticatedClientSession" of specified client attached to it and there is "CODE_TO_TOKEN" action. Otherwise download it from remoteCache
+    // get userSession if it has "authenticatedClientSession" of specified client attached to it. Otherwise download it from remoteCache
     // TODO Probably remove this method once AuthenticatedClientSession.getAction is removed and information is moved to OAuth code JWT instead
-    public UserSessionModel getUserSessionWithClientAndCodeToTokenAction(RealmModel realm, String id, String clientUUID) {
+    public UserSessionModel getUserSessionWithClient(RealmModel realm, String id, String clientUUID) {
 
         return kcSession.sessions().getUserSessionWithPredicate(realm, id, false, (UserSessionModel userSession) -> {
 
             Map<String, AuthenticatedClientSessionModel> authSessions = userSession.getAuthenticatedClientSessions();
-            if (!authSessions.containsKey(clientUUID)) {
-                return false;
-            }
-
-            AuthenticatedClientSessionModel authSession = authSessions.get(clientUUID);
-            return CommonClientSessionModel.Action.CODE_TO_TOKEN.toString().equals(authSession.getAction());
+            return authSessions.containsKey(clientUUID);
 
         });
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
index d990fd1..87bb486 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
@@ -20,7 +20,7 @@ package org.keycloak.services.resources.admin;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.keycloak.common.util.PemUtils;
 import org.keycloak.jose.jws.AlgorithmType;
-import org.keycloak.keys.HmacKeyMetadata;
+import org.keycloak.keys.SecretKeyMetadata;
 import org.keycloak.keys.RsaKeyMetadata;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeyManager;
@@ -65,6 +65,7 @@ public class KeyResource {
         Map<String, String> active = new HashMap<>();
         active.put(AlgorithmType.RSA.name(), keystore.getActiveRsaKey(realm).getKid());
         active.put(AlgorithmType.HMAC.name(), keystore.getActiveHmacKey(realm).getKid());
+        active.put(AlgorithmType.AES.name(), keystore.getActiveAesKey(realm).getKid());
         keys.setActive(active);
 
         List<KeysMetadataRepresentation.KeyMetadataRepresentation> l = new LinkedList<>();
@@ -79,7 +80,7 @@ public class KeyResource {
             r.setCertificate(PemUtils.encodeCertificate(m.getCertificate()));
             l.add(r);
         }
-        for (HmacKeyMetadata m : session.keys().getHmacKeys(realm, true)) {
+        for (SecretKeyMetadata m : session.keys().getHmacKeys(realm, true)) {
             KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
             r.setProviderId(m.getProviderId());
             r.setProviderPriority(m.getProviderPriority());
@@ -88,6 +89,15 @@ public class KeyResource {
             r.setType(AlgorithmType.HMAC.name());
             l.add(r);
         }
+        for (SecretKeyMetadata m : session.keys().getAesKeys(realm, true)) {
+            KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
+            r.setProviderId(m.getProviderId());
+            r.setProviderPriority(m.getProviderPriority());
+            r.setKid(m.getKid());
+            r.setStatus(m.getStatus() != null ? m.getStatus().name() : null);
+            r.setType(AlgorithmType.AES.name());
+            l.add(r);
+        }
 
         keys.setKeys(l);
 
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 78fadf5..20e23de 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -297,7 +297,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
         ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
         clientSessionCode.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
-        clientSessionCode.getCode();
+        clientSessionCode.getOrGenerateCode();
         authSession.setProtocol(client.getProtocol());
         authSession.setRedirectUri(redirectUri);
         authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
@@ -1046,7 +1046,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
         if (clientSessionCode != null) {
             authSession = clientSessionCode.getClientSession();
-            String relayState = clientSessionCode.getCode();
+            String relayState = clientSessionCode.getOrGenerateCode();
             encodedState = IdentityBrokerState.decoded(relayState, authSession.getClient().getClientId());
         }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f6dd8a4..c4ba764 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -740,8 +740,8 @@ public class LoginActionsService {
         authSession.setTimestamp(Time.currentTime());
 
         String clientId = authSession.getClient().getClientId();
-        URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) :
-                Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) ;
+        URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getOrGenerateCode(), clientId) :
+                Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getOrGenerateCode(), clientId) ;
         logger.debugf("Redirecting to '%s' ", redirect);
 
         return Response.status(302).location(redirect).build();
diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
index b5011fb..3f68dd3 100644
--- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -133,7 +133,7 @@ public class SessionCodeChecks {
         }
 
         // object retrieve
-        AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, AuthenticationSessionModel.class);
+        AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, event, AuthenticationSessionModel.class);
         if (authSession != null) {
             return authSession;
         }
@@ -240,7 +240,7 @@ public class SessionCodeChecks {
                 return false;
             }
         } else {
-            ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
+            ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, event, AuthenticationSessionModel.class);
             clientCode = result.getCode();
             if (clientCode == null) {
 
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 01bb2ec..1f456cf 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -77,7 +77,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
 
     @Override
     public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
-        return new Endpoint(realm, callback);
+        return new Endpoint(realm, callback, event);
     }
 
     @Override
@@ -161,6 +161,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
     protected class Endpoint {
         protected RealmModel realm;
         protected AuthenticationCallback callback;
+        protected EventBuilder event;
 
         @Context
         protected KeycloakSession session;
@@ -174,9 +175,12 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
         @Context
         protected UriInfo uriInfo;
 
-        public Endpoint(RealmModel realm, AuthenticationCallback callback) {
+
+
+        public Endpoint(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
             this.realm = realm;
             this.callback = callback;
+            this.event = event;
         }
 
         @GET
@@ -194,7 +198,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
 
                 twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
 
-                AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(state, session, realm, AuthenticationSessionModel.class);
+                AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(state, session, realm, event, AuthenticationSessionModel.class);
 
                 String twitterToken = authSession.getAuthNote(TWITTER_TOKEN);
                 String twitterSecret = authSession.getAuthNote(TWITTER_TOKENSECRET);
@@ -240,7 +244,6 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
         }
 
         private void sendErrorEvent() {
-            EventBuilder event = new EventBuilder(realm, session, clientConnection);
             event.event(EventType.LOGIN);
             event.error("twitter_login_failed");
         }
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
index a57070b..d46a92f 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
@@ -16,6 +16,7 @@
 #
 
 org.keycloak.keys.GeneratedHmacKeyProviderFactory
+org.keycloak.keys.GeneratedAesKeyProviderFactory
 org.keycloak.keys.GeneratedRsaKeyProviderFactory
 org.keycloak.keys.JavaKeystoreKeyProviderFactory
 org.keycloak.keys.ImportedRsaKeyProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
index 964e80d..1954cb8 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
@@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest.resource;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 import javax.ws.rs.Consumes;
@@ -59,6 +60,14 @@ public class TestCacheResource {
         return cache.containsKey(id);
     }
 
+    @GET
+    @Path("/contains-uuid/{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public boolean containsUuid(@PathParam("id") String id) {
+        UUID uuid = UUID.fromString(id);
+        return cache.containsKey(uuid);
+    }
+
 
     @GET
     @Path("/enumerate-keys")
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java
index 1c362ea..c23d241 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java
@@ -39,6 +39,10 @@ public interface TestingCacheResource {
     @Produces(MediaType.APPLICATION_JSON)
     boolean contains(@PathParam("id") String id);
 
+    @GET
+    @Path("/contains-uuid/{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    boolean containsUuid(@PathParam("id") String id);
 
     @GET
     @Path("/enumerate-keys")
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
index ff6f10f..ee38fae 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
@@ -107,7 +107,7 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
         LoginTask loginTask = null;
 
         try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) {
-            loginTask = new LoginTask(httpClient, userSessionId, 100, 1, Arrays.asList(
+            loginTask = new LoginTask(httpClient, userSessionId, 100, 1, false, Arrays.asList(
               createHttpClientContextForUser(httpClient, "test-user@localhost", "password")
             ));
             run(DEFAULT_THREADS, DEFAULT_CLIENTS_COUNT, loginTask);
@@ -132,6 +132,29 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
     }
 
     @Test
+    public void concurrentLoginSingleUserSingleClient() throws Throwable {
+        log.info("*********************************************");
+        long start = System.currentTimeMillis();
+
+        AtomicReference<String> userSessionId = new AtomicReference<>();
+        LoginTask loginTask = null;
+
+        try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) {
+            loginTask = new LoginTask(httpClient, userSessionId, 100, 1, true, Arrays.asList(
+                    createHttpClientContextForUser(httpClient, "test-user@localhost", "password")
+            ));
+            run(DEFAULT_THREADS, DEFAULT_CLIENTS_COUNT, loginTask);
+            int clientSessionsCount = testingClient.testing().getClientSessionsCountInUserSession("test", userSessionId.get());
+            Assert.assertEquals(2, clientSessionsCount);
+        } finally {
+            long end = System.currentTimeMillis() - start;
+            log.infof("Statistics: %s", loginTask == null ? "??" : loginTask.getHistogram());
+            log.info("concurrentLoginSingleUserSingleClient took " + (end/1000) + "s");
+            log.info("*********************************************");
+        }
+    }
+
+    @Test
     public void concurrentLoginMultipleUsers() throws Throwable {
         log.info("*********************************************");
         long start = System.currentTimeMillis();
@@ -140,7 +163,7 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
         LoginTask loginTask = null;
 
         try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) {
-            loginTask = new LoginTask(httpClient, userSessionId, 100, 1, Arrays.asList(
+            loginTask = new LoginTask(httpClient, userSessionId, 100, 1, false, Arrays.asList(
               createHttpClientContextForUser(httpClient, "test-user@localhost", "password"),
               createHttpClientContextForUser(httpClient, "john-doh@localhost", "password"),
               createHttpClientContextForUser(httpClient, "roleRichUser", "password")
@@ -157,6 +180,60 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
         }
     }
 
+
+    @Test
+    public void concurrentCodeReuseShouldFail() throws Throwable {
+        log.info("*********************************************");
+        long start = System.currentTimeMillis();
+
+
+        for (int i=0 ; i<10 ; i++) {
+            OAuthClient oauth1 = new OAuthClient();
+            oauth1.init(adminClient, driver);
+            oauth1.clientId("client0");
+
+            OAuthClient.AuthorizationEndpointResponse resp = oauth1.doLogin("test-user@localhost", "password");
+            String code = resp.getCode();
+            Assert.assertNotNull(code);
+            String codeURL = driver.getCurrentUrl();
+
+
+            AtomicInteger codeToTokenSuccessCount = new AtomicInteger(0);
+            AtomicInteger codeToTokenErrorsCount = new AtomicInteger(0);
+
+            KeycloakRunnable codeToTokenTask = new KeycloakRunnable() {
+
+                @Override
+                public void run(int threadIndex, Keycloak keycloak, RealmResource realm) throws Throwable {
+                    log.infof("Trying to execute codeURL: %s, threadIndex: %i", codeURL, threadIndex);
+
+                    OAuthClient.AccessTokenResponse resp = oauth1.doAccessTokenRequest(code, "password");
+                    if (resp.getAccessToken() != null && resp.getError() == null) {
+                        codeToTokenSuccessCount.incrementAndGet();
+                    } else if (resp.getAccessToken() == null && resp.getError() != null) {
+                        codeToTokenErrorsCount.incrementAndGet();
+                    }
+                }
+
+            };
+
+            run(DEFAULT_THREADS, DEFAULT_THREADS, codeToTokenTask);
+
+            oauth1.openLogout();
+
+            Assert.assertEquals(1, codeToTokenSuccessCount.get());
+            Assert.assertEquals(DEFAULT_THREADS - 1, codeToTokenErrorsCount.get());
+
+            log.infof("Iteration %i passed successfully", i);
+        }
+
+        long end = System.currentTimeMillis() - start;
+        log.info("concurrentCodeReuseShouldFail took " + (end/1000) + "s");
+        log.info("*********************************************");
+
+    }
+
+
     protected String getPageContent(String url, CloseableHttpClient httpClient, HttpClientContext context) throws IOException {
         HttpGet request = new HttpGet(url);
 
@@ -237,6 +314,7 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
         return m;
     }
 
+
     public class LoginTask implements KeycloakRunnable {
 
         private final AtomicInteger clientIndex = new AtomicInteger();
@@ -256,9 +334,10 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
         private final int retryCount;
         private final AtomicInteger[] retryHistogram;
         private final AtomicInteger totalInvocations = new AtomicInteger();
+        private final boolean sameClient;
         private final List<HttpClientContext> clientContexts;
 
-        public LoginTask(CloseableHttpClient httpClient, AtomicReference<String> userSessionId, int retryDelayMs, int retryCount, List<HttpClientContext> clientContexts) {
+        public LoginTask(CloseableHttpClient httpClient, AtomicReference<String> userSessionId, int retryDelayMs, int retryCount, boolean sameClient, List<HttpClientContext> clientContexts) {
             this.httpClient = httpClient;
             this.userSessionId = userSessionId;
             this.retryDelayMs = retryDelayMs;
@@ -267,12 +346,13 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
             for (int i = 0; i < retryHistogram.length; i ++) {
                 retryHistogram[i] = new AtomicInteger();
             }
+            this.sameClient = sameClient;
             this.clientContexts = clientContexts;
         }
 
         @Override
         public void run(int threadIndex, Keycloak keycloak, RealmResource realm) throws Throwable {
-            int i = clientIndex.getAndIncrement();
+            int i = sameClient ? 0 : clientIndex.getAndIncrement();
             OAuthClient oauth1 = oauthClient.get();
             oauth1.clientId("client" + i);
             log.infof("%d [%s]: Accessing login page for %s", threadIndex, Thread.currentThread().getName(), oauth1.getClientId());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ConcurrentLoginCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ConcurrentLoginCrossDCTest.java
index b710943..4cd0256 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ConcurrentLoginCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ConcurrentLoginCrossDCTest.java
@@ -75,7 +75,7 @@ public class ConcurrentLoginCrossDCTest extends ConcurrentLoginTest {
         LoginTask loginTask = null;
 
         try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) {
-            loginTask = new LoginTask(httpClient, userSessionId, LOGIN_TASK_DELAY_MS, LOGIN_TASK_RETRIES, Arrays.asList(
+            loginTask = new LoginTask(httpClient, userSessionId, LOGIN_TASK_DELAY_MS, LOGIN_TASK_RETRIES, false, Arrays.asList(
               createHttpClientContextForUser(httpClient, "test-user@localhost", "password")
             ));
             HttpUriRequest request = handleLogin(getPageContent(oauth.getLoginFormUrl(), httpClient, HttpClientContext.create()), "test-user@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java
index 961026d..efddeea 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java
@@ -168,7 +168,7 @@ public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
         rep.getConfig().putSingle("secretSize", "1234");
 
         Response response = adminClient.realm("test").components().add(rep);
-        assertErrror(response, "'Secret size' should be 32, 64, 128, 256 or 512");
+        assertErrror(response, "'Secret size' should be 16, 24, 32, 64, 128, 256 or 512");
     }
 
     protected void assertErrror(Response response, String error) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 6a9aa65..601231f 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -307,7 +307,6 @@ public class AccessTokenTest extends AbstractKeycloakTest {
         events.expectCodeToToken(codeId, sessionId)
                 .removeDetail(Details.TOKEN_ID)
                 .user((String) null)
-                .session((String) null)
                 .removeDetail(Details.REFRESH_TOKEN_ID)
                 .removeDetail(Details.REFRESH_TOKEN_TYPE)
                 .error(Errors.INVALID_CODE).assertEvent();
@@ -334,8 +333,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
 
         setTimeOffset(0);
 
-        AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null);
-        expectedEvent.error("invalid_code")
+        AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, codeId);
+        expectedEvent.error("expired_code")
                 .removeDetail(Details.TOKEN_ID)
                 .removeDetail(Details.REFRESH_TOKEN_ID)
                 .removeDetail(Details.REFRESH_TOKEN_TYPE)
@@ -380,7 +379,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
             response = oauth.doAccessTokenRequest(code, "password");
             Assert.assertEquals(400, response.getStatusCode());
 
-            AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null);
+            AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, codeId);
             expectedEvent.error("invalid_code")
                     .removeDetail(Details.TOKEN_ID)
                     .removeDetail(Details.REFRESH_TOKEN_ID)
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 7577990..7ab9512 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -22,6 +22,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.models.Constants;
@@ -73,7 +74,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
         Assert.assertNull(response.getError());
 
         String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
-        assertCode(codeId, response.getCode());
     }
 
     @Test
@@ -89,7 +89,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
         String code = driver.findElement(By.id(OAuth2Constants.CODE)).getAttribute("value");
 
         String codeId = events.expectLogin().detail(Details.REDIRECT_URI, "http://localhost:8180/auth/realms/test/protocol/openid-connect/oauth/oob").assertEvent().getDetails().get(Details.CODE_ID);
-        assertCode(codeId, code);
 
         ClientManager.realm(adminClient.realm("test")).clientId("test-app").removeRedirectUris(Constants.INSTALLED_APP_URN);
     }
@@ -104,7 +103,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
         Assert.assertNotNull(response.getCode());
 
         String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
-        assertCode(codeId, response.getCode());
     }
 
     @Test
@@ -119,7 +117,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
         Assert.assertNull(response.getError());
 
         String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
-        assertCode(codeId, response.getCode());
     }
 
     @Test
@@ -151,11 +148,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
         assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", state);
 
         String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
-        assertCode(codeId, code);
-    }
-
-    private void assertCode(String expectedCodeId, String actualCode) {
-        assertEquals(expectedCodeId, actualCode.split("\\.")[2]);
     }
 
 }
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index 846267f..e616331 100755
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -33,6 +33,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.models.UserManager;
+import org.keycloak.sessions.CommonClientSessionModel;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
 import java.util.Arrays;
@@ -169,14 +170,14 @@ public class UserSessionProviderTest {
         int time = clientSession.getTimestamp();
         assertEquals(null, clientSession.getAction());
 
-        clientSession.setAction(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name());
+        clientSession.setAction(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name());
         clientSession.setTimestamp(time + 10);
 
         kc.stopSession(session, true);
         session = kc.startSession();
 
         AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessions().get(clientUUID);
-        assertEquals(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
+        assertEquals(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name(), updated.getAction());
         assertEquals(time + 10, updated.getTimestamp());
     }
 
@@ -190,11 +191,11 @@ public class UserSessionProviderTest {
         UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
         AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUUID);
 
-        clientSession.setAction(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name());
+        clientSession.setAction(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name());
         clientSession.setNote("foo", "bar");
 
         AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessions().get(clientUUID);
-        assertEquals(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
+        assertEquals(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name(), updated.getAction());
         assertEquals("bar", updated.getNote("foo"));
     }