keycloak-memoizeit

resolve conflicts

11/11/2015 9:06:39 PM

Changes

saml/saml-core/src/main/java/org/keycloak/saml/common/util/PBEUtils.java 85(+0 -85)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JBossJiraParser.java 43(+0 -43)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Jira.java 28(+0 -28)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JiraTestExecutionDecider.java 61(+0 -61)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Status.java 36(+0 -36)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Bindings.java 146(+0 -146)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/OTPPolicy.java 85(+0 -85)

Details

diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index 6da630f..a677419 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -92,4 +92,9 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> 
     public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
 
     }
+
+    @Override
+    public IdentityProviderDataMarshaller getMarshaller() {
+        return new DefaultDataMarshaller();
+    }
 }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
new file mode 100644
index 0000000..3f8fcf2
--- /dev/null
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
@@ -0,0 +1,40 @@
+package org.keycloak.broker.provider;
+
+import java.io.IOException;
+
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DefaultDataMarshaller implements IdentityProviderDataMarshaller {
+
+    @Override
+    public String serialize(Object value) {
+        if (value instanceof String) {
+            return (String) value;
+        } else {
+            try {
+                byte[] bytes = JsonSerialization.writeValueAsBytes(value);
+                return Base64Url.encode(bytes);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+
+    @Override
+    public <T> T deserialize(String serialized, Class<T> clazz) {
+        if (clazz.equals(String.class)) {
+            return clazz.cast(serialized);
+        } else {
+            byte[] bytes = Base64Url.decode(serialized);
+            try {
+                return JsonSerialization.readValue(bytes, clazz);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+}
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index 1d775ee..42eb6fe 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -103,4 +103,10 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
      */
     Response export(UriInfo uriInfo, RealmModel realm, String format);
 
+    /**
+     * Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
+     * @return
+     */
+    IdentityProviderDataMarshaller getMarshaller();
+
 }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
new file mode 100644
index 0000000..7e57653
--- /dev/null
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
@@ -0,0 +1,12 @@
+package org.keycloak.broker.provider;
+
+/**
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface IdentityProviderDataMarshaller {
+
+    String serialize(Object obj);
+    <T> T deserialize(String serialized, Class<T> clazz);
+
+}
diff --git a/broker/saml/pom.xml b/broker/saml/pom.xml
index 858a7ba..21a0099 100755
--- a/broker/saml/pom.xml
+++ b/broker/saml/pom.xml
@@ -45,6 +45,11 @@
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
new file mode 100644
index 0000000..61f4d8a
--- /dev/null
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
@@ -0,0 +1,88 @@
+package org.keycloak.broker.saml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import javax.xml.stream.XMLEventReader;
+
+import org.keycloak.broker.provider.DefaultDataMarshaller;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.StaxUtil;
+import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
+import org.keycloak.saml.processing.core.parsers.util.SAMLParserUtil;
+import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
+import org.keycloak.saml.processing.core.saml.v2.writers.SAMLResponseWriter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLDataMarshaller extends DefaultDataMarshaller {
+
+    @Override
+    public String serialize(Object obj) {
+
+        // Lame impl, but hopefully sufficient for now. See if something better is needed...
+        if (obj.getClass().getName().startsWith("org.keycloak.dom.saml")) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+            try {
+                if (obj instanceof ResponseType) {
+                    ResponseType responseType = (ResponseType) obj;
+                    SAMLResponseWriter samlWriter = new SAMLResponseWriter(StaxUtil.getXMLStreamWriter(bos));
+                    samlWriter.write(responseType);
+                } else if (obj instanceof AssertionType) {
+                    AssertionType assertion = (AssertionType) obj;
+                    SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
+                    samlWriter.write(assertion);
+                } else if (obj instanceof AuthnStatementType) {
+                    AuthnStatementType authnStatement = (AuthnStatementType) obj;
+                    SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
+                    samlWriter.write(authnStatement, true);
+                } else {
+                    throw new IllegalArgumentException("Don't know how to serialize object of type " + obj.getClass().getName());
+                }
+            } catch (ProcessingException pe) {
+                throw new RuntimeException(pe);
+            }
+
+            return new String(bos.toByteArray());
+        } else {
+            return super.serialize(obj);
+        }
+    }
+
+    @Override
+    public <T> T deserialize(String serialized, Class<T> clazz) {
+        if (clazz.getName().startsWith("org.keycloak.dom.saml")) {
+            String xmlString = serialized;
+
+            try {
+                if (clazz.equals(ResponseType.class) || clazz.equals(AssertionType.class)) {
+                    byte[] bytes = xmlString.getBytes();
+                    InputStream is = new ByteArrayInputStream(bytes);
+                    Object respType = new SAMLParser().parse(is);
+                    return clazz.cast(respType);
+                } else if (clazz.equals(AuthnStatementType.class)) {
+                    byte[] bytes = xmlString.getBytes();
+                    InputStream is = new ByteArrayInputStream(bytes);
+                    XMLEventReader xmlEventReader = new SAMLParser().createEventReader(is);
+                    AuthnStatementType authnStatement = SAMLParserUtil.parseAuthnStatement(xmlEventReader);
+                    return clazz.cast(authnStatement);
+                } else {
+                    throw new IllegalArgumentException("Don't know how to deserialize object of type " + clazz.getName());
+                }
+            } catch (ParsingException pe) {
+                throw new RuntimeException(pe);
+            }
+
+        } else {
+            return super.deserialize(serialized, clazz);
+        }
+    }
+
+}
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 248c4de..0014470 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
 import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.dom.saml.v2.assertion.AssertionType;
 import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
@@ -263,4 +264,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
         return SignatureAlgorithm.RSA_SHA256;
     }
 
+    @Override
+    public IdentityProviderDataMarshaller getMarshaller() {
+        return new SAMLDataMarshaller();
+    }
 }
diff --git a/broker/saml/src/test/java/org/keycloak/broker/saml/SAMLDataMarshallerTest.java b/broker/saml/src/test/java/org/keycloak/broker/saml/SAMLDataMarshallerTest.java
new file mode 100644
index 0000000..cf6e4f8
--- /dev/null
+++ b/broker/saml/src/test/java/org/keycloak/broker/saml/SAMLDataMarshallerTest.java
@@ -0,0 +1,64 @@
+package org.keycloak.broker.saml;
+
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLDataMarshallerTest {
+
+    private static final String TEST_RESPONSE = "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_4804cf50-cd96-4b92-823e-89adaa0c78ba\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.920Z\" Destination=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\" InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"></samlp:StatusCode></samlp:Status><saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
+
+    private static final String TEST_ASSERTION = "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>";
+
+    private static final String TEST_AUTHN_TYPE = "<saml:AuthnStatement xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
+
+    @Test
+    public void testParseResponse() throws Exception {
+        SAMLDataMarshaller serializer = new SAMLDataMarshaller();
+        ResponseType responseType = serializer.deserialize(TEST_RESPONSE, ResponseType.class);
+
+        // test ResponseType
+        Assert.assertEquals(responseType.getID(), "ID_4804cf50-cd96-4b92-823e-89adaa0c78ba");
+        Assert.assertEquals(responseType.getDestination(), "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint");
+        Assert.assertEquals(responseType.getIssuer().getValue(), "http://localhost:8082/auth/realms/realm-with-saml-idp-basic");
+        Assert.assertEquals(responseType.getAssertions().get(0).getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
+
+        // back to String
+        String serialized = serializer.serialize(responseType);
+        Assert.assertEquals(TEST_RESPONSE, serialized);
+    }
+
+    @Test
+    public void testParseAssertion() throws Exception {
+        SAMLDataMarshaller serializer = new SAMLDataMarshaller();
+        AssertionType assertion = serializer.deserialize(TEST_ASSERTION, AssertionType.class);
+
+        // test assertion
+        Assert.assertEquals(assertion.getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
+        Assert.assertEquals(((NameIDType) assertion.getSubject().getSubType().getBaseID()).getValue(), "test-user");
+
+        // back to String
+        String serialized = serializer.serialize(assertion);
+        Assert.assertEquals(TEST_ASSERTION, serialized);
+    }
+
+    @Test
+    public void testParseAuthnType() throws Exception {
+        SAMLDataMarshaller serializer = new SAMLDataMarshaller();
+        AuthnStatementType authnStatement =  serializer.deserialize(TEST_AUTHN_TYPE, AuthnStatementType.class);
+
+        // test authnStatement
+        Assert.assertEquals(authnStatement.getSessionIndex(), "fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5");
+
+        // back to String
+        String serialized = serializer.serialize(authnStatement);
+        Assert.assertEquals(TEST_AUTHN_TYPE, serialized);
+    }
+}
diff --git a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
index cec9ea9..1ade852 100644
--- a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
+++ b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
@@ -13,7 +13,7 @@ public class ObjectUtil {
      * @param str2
      * @return true if both strings are null or equal
      */
-    public static boolean isEqualOrNull(Object str1, Object str2) {
+    public static boolean isEqualOrBothNull(Object str1, Object str2) {
         if (str1 == null && str2 == null) {
             return true;
         }
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index ba88669..1120252 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -37,6 +37,14 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
+
+        <addColumn tableName="IDENTITY_PROVIDER">
+            <column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
+
         <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="KEYCLOAK_GROUP" constraintName="FK_GROUP_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
 
@@ -51,6 +59,5 @@
         <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
 
 
-
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
index 1e74002..0dc7f0a 100755
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
@@ -46,12 +46,14 @@ public class IdentityProviderRepresentation {
      * @see #UPFLM_MISSING
      * @see #UPFLM_OFF
      */
+    @Deprecated
     protected String updateProfileFirstLoginMode = UPFLM_ON;
 
     protected boolean trustEmail;
     protected boolean storeToken;
     protected boolean addReadTokenRoleOnCreate;
     protected boolean authenticateByDefault;
+    protected String firstBrokerLoginFlowAlias;
     protected Map<String, String> config = new HashMap<String, String>();
 
     public String getInternalId() {
@@ -106,15 +108,17 @@ public class IdentityProviderRepresentation {
     }
 
     /**
-     * @return see {@link #updateProfileFirstLoginMode}
+     * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator
      */
+    @Deprecated
     public String getUpdateProfileFirstLoginMode() {
         return updateProfileFirstLoginMode;
     }
 
     /**
-     * @param updateProfileFirstLoginMode see {@link #updateProfileFirstLoginMode}
+     * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator
      */
+    @Deprecated
     public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
         this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
     }
@@ -127,6 +131,14 @@ public class IdentityProviderRepresentation {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowAlias() {
+        return firstBrokerLoginFlowAlias;
+    }
+
+    public void setFirstBrokerLoginFlowAlias(String firstBrokerLoginFlowAlias) {
+        this.firstBrokerLoginFlowAlias = firstBrokerLoginFlowAlias;
+    }
+
     public boolean isStoreToken() {
         return this.storeToken;
     }
diff --git a/distribution/docs-dist/assembly.xml b/distribution/docs-dist/assembly.xml
index 00862c2..7da1a5c 100755
--- a/distribution/docs-dist/assembly.xml
+++ b/distribution/docs-dist/assembly.xml
@@ -9,7 +9,7 @@
 
     <fileSets>
         <fileSet>
-            <directory>../../target/site/apidocs</directory>
+            <directory>target/site/apidocs</directory>
             <outputDirectory>javadocs</outputDirectory>
         </fileSet>
         <fileSet>
diff --git a/distribution/docs-dist/src/index.html b/distribution/docs-dist/src/index.html
index f196cd5..6aeffbe 100755
--- a/distribution/docs-dist/src/index.html
+++ b/distribution/docs-dist/src/index.html
@@ -1,11 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        td { padding: 5px; }
+    </style>
+</head>
+
+<body>
 <h1>Keyloak Documentation</h1>
-<ul>
-    <li><a href="userguide/keycloak-server/html/index.html">Server and Keycloak Adapter Userguide HTML</a></li>
-    <li><a href="userguide/keycloak-server/html_single/index.html">Server and Keycloak Adapter Userguide HTML Single Page</a></li>
-    <li><a href="userguide/keycloak-server/pdf/keycloak-reference-guide-en-US.pdf">Server and Keycloak Adapter Userguide PDF</a></li>
-    <li><a href="userguide/saml-client-adapter/html/index.html">SAML Client Adapter Userguide HTML</a></li>
-    <li><a href="userguide/saml-client-adapter/html_single/index.html">>SAML Client Adapter Userguide HTML Single Page</a></li>
-    <li><a href="userguide/saml-client-adapter/pdf/keycloak-reference-guide-en-US.pdf">SAML Client Adapter Userguide PDF</a></li>
-    <li><a href="rest-api/overview-index.html">Admin REST API</a></li>
-    <li><a href="javadocs/index.html">Javadocs</a></li>
-</ul>
\ No newline at end of file
+<table>
+    <tr>
+        <td>Server and Keycloak Adapter Userguide</td>
+        <td><a href="userguide/keycloak-server/html/index.html">HTML</a></td>
+        <td><a href="userguide/keycloak-server/html_single/index.html">HTML Single Page</a></td>
+        <td><a href="userguide/keycloak-server/pdf/keycloak-reference-guide-en-US.pdf">PDF</a></td>
+    </tr>
+    <tr>
+        <td>SAML Client Adapter Userguide</td>
+        <td><a href="userguide/saml-client-adapter/html/index.html">HTML</a></td>
+        <td><a href="userguide/saml-client-adapter/html_single/index.html">HTML Single Page</a></td>
+        <td><a href="userguide/saml-client-adapter/pdf/keycloak-saml-adapter-reference-guide-en-US.pdf">PDF</a></td>
+    </tr>
+    <tr>
+        <td>Admin REST API</td>
+        <td colspan="3"><a href="rest-api/index.html">HTML</a></td>
+    </tr>
+    <tr>
+        <td>Javadocs</td>
+        <td colspan="3"><a href="javadocs/index.html">HTML</a></td>
+    </tr>
+</body>
+</html>
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/assembly.xml b/distribution/feature-packs/server-feature-pack/assembly.xml
index 1e142ae..4cd92ce 100644
--- a/distribution/feature-packs/server-feature-pack/assembly.xml
+++ b/distribution/feature-packs/server-feature-pack/assembly.xml
@@ -66,4 +66,15 @@
             </includes>
         </fileSet>
     </fileSets>
+
+    <files>
+        <file>
+            <source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
+            <outputDirectory>content/domain/servers/server-one/configuration</outputDirectory>
+        </file>
+        <file>
+            <source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
+            <outputDirectory>content/domain/servers/server-two/configuration</outputDirectory>
+        </file>
+    </files>
 </assembly>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml
index 2dde48f..582aa42 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml
@@ -10,7 +10,7 @@
       <subsystem>ee.xml</subsystem>
       <subsystem>ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem>infinispan.xml</subsystem>
+      <subsystem>keycloak-infinispan.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
       <subsystem>jdr.xml</subsystem>
@@ -41,7 +41,7 @@
       <subsystem>ee.xml</subsystem>
       <subsystem supplement="ha">ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem supplement="ha">infinispan.xml</subsystem>
+      <subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
       <subsystem>jdr.xml</subsystem>
@@ -74,7 +74,7 @@
       <subsystem supplement="full">ee.xml</subsystem>
       <subsystem supplement="full">ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem>infinispan.xml</subsystem>
+      <subsystem>keycloak-infinispan.xml</subsystem>
       <subsystem>iiop-openjdk.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
@@ -108,7 +108,7 @@
       <subsystem supplement="full">ee.xml</subsystem>
       <subsystem supplement="full-ha">ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem supplement="ha">infinispan.xml</subsystem>
+      <subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
       <subsystem>iiop-openjdk.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index 650ed76..b0a443d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -80,15 +80,33 @@
     <section>
         <title>Version specific migration</title>
         <section>
+            <title>Migrating to 1.7.0.CR1</title>
+            <simplesect>
+                <title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
+                <para>
+                    In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
+                    when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user
+                    yet linked to the social account. As part of this work, we added option <literal>First Login Flow</literal> to identity providers where
+                    you can specify the flow and then you can configure this flow under <literal>Authentication</literal> tab in admin console.
+                </para>
+                <para>
+                    We also removed the option <literal>Update Profile On First Login</literal> from the Identity provider settings and moved it
+                    to the configuration of <literal>Review Profile</literal> authenticator. So once you specify which flow should be used for your
+                    Identity provider (by default it's <literal>First Broker Login</literal> flow), you go to <literal>Authentication</literal> tab, select the flow
+                    and then you configure the option under <literal>Review Profile</literal> authenticator.
+                </para>
+            </simplesect>
+        </section>
+        <section>
             <title>Migrating to 1.6.0.Final</title>
             <simplesect>
-                <title>Refresh tokens are not reusable anymore</title>
+                <title>Option that refresh tokens are not reusable anymore</title>
                 <para>
-                    Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak no longer permits
-                    this by default. When a refresh token is used to obtain a new access token a new refresh token is also
-                    included. This new refresh token should be used next time the access token is refreshed. If this is
-                    a problem for you it's possible to enable reuse of refresh tokens in the admin console under token
-                    settings.
+                    Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak still permits this,
+                    but also have an option <literal>Revoke refresh token</literal> to disallow it. Option is in in admin console under token settings.
+                    When a refresh token is used to obtain a new access token a new refresh token is also
+                    included. When option is enabled, then this new refresh token should be used next time the access token is refreshed.
+                    It won't be possible to reuse old refresh token multiple times.
                 </para>
             </simplesect>
             <simplesect>
diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java
index d7ca253..34c5979 100755
--- a/events/api/src/main/java/org/keycloak/events/Errors.java
+++ b/events/api/src/main/java/org/keycloak/events/Errors.java
@@ -53,4 +53,5 @@ public interface Errors {
     String EMAIL_SEND_FAILED = "email_send_failed";
     String INVALID_EMAIL = "invalid_email";
     String IDENTITY_PROVIDER_LOGIN_FAILURE = "identity_provider_login_failure";
+    String IDENTITY_PROVIDER_ERROR = "identity_provider_error";
 }
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index 0a8b3ae..5cffe78 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -60,6 +60,8 @@ public enum EventType {
 
     IDENTITY_PROVIDER_LOGIN(false),
     IDENTITY_PROVIDER_LOGIN_ERROR(false),
+    IDENTITY_PROVIDER_FIRST_LOGIN(true),
+    IDENTITY_PROVIDER_FIRST_LOGIN_ERROR(true),
     IDENTITY_PROVIDER_RESPONSE(false),
     IDENTITY_PROVIDER_RESPONSE_ERROR(false),
     IDENTITY_PROVIDER_RETRIEVE_TOKEN(false),
diff --git a/examples/js-console/src/main/webapp/WEB-INF/web.xml b/examples/js-console/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..a7460c2
--- /dev/null
+++ b/examples/js-console/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+    <module-name>js-console</module-name>
+</web-app>
\ No newline at end of file
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
index ea3f953..f38eeb5 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
@@ -17,6 +17,7 @@ import javax.security.auth.login.LoginException;
 
 import org.jboss.logging.Logger;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.models.ModelException;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -54,6 +55,8 @@ public class KerberosUsernamePasswordAuthenticator {
             String message = le.getMessage();
             logger.debug("Message from kerberos: " + message);
 
+            checkKerberosServerAvailable(le);
+
             // Bit cumbersome, but seems to work with tested kerberos servers
             boolean exists = (!message.contains("Client not found"));
             return exists;
@@ -74,11 +77,19 @@ public class KerberosUsernamePasswordAuthenticator {
             logoutSubject();
             return true;
         } catch (LoginException le) {
+            checkKerberosServerAvailable(le);
+
             logger.debug("Failed to authenticate user " + username, le);
             return false;
         }
     }
 
+    protected void checkKerberosServerAvailable(LoginException le) {
+        if (le.getMessage().contains("Port Unreachable")) {
+            throw new ModelException("Kerberos unreachable", le);
+        }
+    }
+
 
     /**
      * Returns true if user was successfully authenticated against Kerberos
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
index 97f2d66..33d080d 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
@@ -126,4 +126,4 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
index f85ebff..3d2eeea 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -150,4 +150,4 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
new file mode 100644
index 0000000..40743f8
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
@@ -0,0 +1,154 @@
+doSave=Guardar
+doCancel=Cancelar
+doLogOutAllSessions=Desconectar de todas las sesiones
+doRemove=Eliminar
+doAdd=A\u00F1adir
+doSignOut=Desconectar
+
+editAccountHtmlTtile=Editar cuenta
+federatedIdentitiesHtmlTitle=Identidades federadas
+accountLogHtmlTitle=Registro de la cuenta
+changePasswordHtmlTitle=Cambiar contrase\u00F1a
+sessionsHtmlTitle=Sesiones
+accountManagementTitle=Gesti\u00F3n de Cuenta Keycloak
+authenticatorTitle=Autenticador
+applicationsHtmlTitle=Aplicaciones
+
+authenticatorCode=C\u00F3digo de un solo uso
+email=Email
+firstName=Nombre
+givenName=Nombre de pila
+fullName=Nombre completo
+lastName=Apellidos
+familyName=Apellido
+password=Contrase\u00F1a
+passwordConfirm=Confirma la contrase\u00F1a
+passwordNew=Nueva contrase\u00F1a
+username=Usuario
+address=Direcci\u00F3n
+street=Calle
+locality=Ciudad o Municipio
+region=Estado, Provincia, o Regi\u00F3n
+postal_code=C\u00F3digo Postal
+country=Pa\u00EDs
+emailVerified=Email verificado
+gssDelegationCredential=GSS Delegation Credential
+
+role_admin=Administrador
+role_realm-admin=Administrador del dominio
+role_create-realm=Crear dominio
+role_view-realm=Ver dominio
+role_view-users=Ver usuarios
+role_view-applications=Ver aplicaciones
+role_view-clients=Ver clientes
+role_view-events=Ver eventos
+role_view-identity-providers=Ver proveedores de identidad
+role_manage-realm=Gestionar dominio
+role_manage-users=Gestionar usuarios
+role_manage-applications=Gestionar aplicaciones
+role_manage-identity-providers=Gestionar proveedores de identidad
+role_manage-clients=Gestionar clientes
+role_manage-events=Gestionar eventos
+role_view-profile=Ver perfil
+role_manage-account=Gestionar cuenta
+role_read-token=Leer token
+role_offline-access=Acceso sin conexi\u00F3n
+client_account=Cuenta
+client_security-admin-console=Consola de Administraci\u00F3n de Seguridad
+client_realm-management=Gesti\u00F3n de dominio
+client_broker=Broker
+
+
+requiredFields=Campos obligatorios
+allFieldsRequired=Todos los campos obligatorios
+
+backToApplication=&laquo; Volver a la aplicaci\u00F3n
+backTo=Volver a {0}
+
+date=Fecha
+event=Evento
+ip=IP
+client=Cliente
+clients=Clientes
+details=Detalles
+started=Iniciado
+lastAccess=\u00DAltimo acceso
+expires=Expira
+applications=Aplicaciones
+
+account=Cuenta
+federatedIdentity=Identidad federada
+authenticator=Autenticador
+sessions=Sesiones
+log=Regisro
+
+application=Aplicaci\u00F3n
+availablePermissions=Permisos disponibles
+grantedPermissions=Permisos concedidos
+grantedPersonalInfo=Informaci\u00F3n personal concedida
+additionalGrants=Permisos adicionales
+action=Acci\u00F3n
+inResource=en
+fullAccess=Acceso total
+offlineToken=C\u00F3digo de autorizaci\u00F3n offline
+revoke=Revocar permiso
+
+configureAuthenticators=Autenticadores configurados
+mobile=M\u00F3vil
+totpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
+totpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
+totpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
+
+missingUsernameMessage=Por favor indica tu usuario.
+missingFirstNameMessage=Por favor indica el nombre.
+invalidEmailMessage=Email no v\u00E1lido
+missingLastNameMessage=Por favor indica tus apellidos.
+missingEmailMessage=Por favor indica el email.
+missingPasswordMessage=Por favor indica tu contrase\u00F1a.
+notMatchPasswordMessage=Las contrase\u00F1as no coinciden.
+
+missingTotpMessage=Por favor indica tu c\u00F3digo de autenticaci\u00F3n
+invalidPasswordExistingMessage=La contrase\u00F1a actual no es correcta.
+invalidPasswordConfirmMessage=La confirmaci\u00F3n de contrase\u00F1a no coincide.
+invalidTotpMessage=El c\u00F3digo de autenticaci\u00F3n no es v\u00E1lido.
+
+usernameExistsMessage=El usuario ya existe
+emailExistsMessage=El email ya existe
+
+readOnlyUserMessage=No puedes actualizar tu usuario porque tu cuenta es de solo lectura.
+readOnlyPasswordMessage=No puedes actualizar tu contrase\u00F1a porque tu cuenta es de solo lectura.
+
+successTotpMessage=Aplicaci\u00F3n de autenticaci\u00F3n m\u00F3vil configurada.
+successTotpRemovedMessage=Aplicaci\u00F3n de autenticaci\u00F3n m\u00F3vil eliminada.
+
+successGrantRevokedMessage=Permiso revocado correctamente
+
+accountUpdatedMessage=Tu cuenta se ha actualizado.
+accountPasswordUpdatedMessage=Tu contrase\u00F1a se ha actualizado.
+
+missingIdentityProviderMessage=Proveedor de identidad no indicado.
+invalidFederatedIdentityActionMessage=Acci\u00F3n no v\u00E1lida o no indicada.
+identityProviderNotFoundMessage=No se encontr\u00F3 un proveedor de identidad.
+federatedIdentityLinkNotActiveMessage=Esta identidad ya no est\u00E1 activa
+federatedIdentityRemovingLastProviderMessage=No puedes eliminar la \u00FAltima identidad federada porque no tienes fijada una contrase\u00F1a.
+identityProviderRedirectErrorMessage=Error en la redirecci\u00F3n al proveedor de identidad
+identityProviderRemovedMessage=Proveedor de identidad borrado correctamente.
+
+accountDisabledMessage=La cuenta est\u00E1 desactivada, contacta con el administrador.
+
+accountTemporarilyDisabledMessage=La cuenta est\u00E1 temporalmente desactivada, contacta con el administrador o int\u00E9ntalo de nuevo m\u00E1s tarde.
+invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
+invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
+invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
+invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
+invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular.
+invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as.
+
+locale_de=German
+locale_en=English
+locale_it=Italian
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_fr=Fran\u00E7ais
+locale_es=Espa\u00F1ol
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties
index 6831687..56e41d4 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties
@@ -10,7 +10,7 @@ doSignOut=D\u00e9connexion
 
 editAccountHtmlTtile=Edition du compte
 federatedIdentitiesHtmlTitle=Identit\u00e9s f\u00e9d\u00e9r\u00e9es
-accountLogHtmlTitle=Acces au compte
+accountLogHtmlTitle=Acc\u00e8s au compte
 changePasswordHtmlTitle=Changer de mot de passe
 sessionsHtmlTitle=Sessions
 accountManagementTitle=Gestion de Compte Keycloak
@@ -22,7 +22,7 @@ email=Courriel
 firstName=Nom
 givenName=Pr\u00e9nom
 fullName=Nom Complet
-lastName=Last name
+lastName=Nom
 familyName=Nom de Famille
 password=Mot de passe
 passwordConfirm=Confirmation
@@ -31,7 +31,7 @@ username=Compte
 address=Adresse
 street=Rue
 locality=Ville ou Localit\u00e9
-region=State, Province, or R\u00e9gion
+region=\u00c9tat, Province ou R\u00e9gion
 postal_code=Code Postal
 country=Pays
 emailVerified=Courriel v\u00e9rifi\u00e9
@@ -50,7 +50,7 @@ role_manage-realm=G\u00e9rer le domaine
 role_manage-users=G\u00e9rer les utilisateurs
 role_manage-applications=G\u00e9rer les applications
 role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s
-role_manage-clients=G\u00e9rer les  clients
+role_manage-clients=G\u00e9rer les clients
 role_manage-events=G\u00e9rer les  \u00e9v\u00e9nements
 role_view-profile=Voir le profile
 role_manage-account=G\u00e9rer le compte
@@ -63,7 +63,7 @@ client_broker=Broker
 
 
 requiredFields=Champs obligatoires
-allFieldsRequired=Tous les champs obligatoires
+allFieldsRequired=Tous les champs sont obligatoires
 
 backToApplication=&laquo; Revenir \u00e0 l''application
 backTo=Revenir \u00e0 {0}
@@ -74,9 +74,9 @@ ip=IP
 client=Client
 clients=Clients
 details=D\u00e9tails
-started=S\u00e9lectionn\u00e9
+started=D\u00e9but
 lastAccess=Dernier acc\u00e8s
-expires=Expires
+expires=Expiration
 applications=Applications
 
 account=Compte
@@ -88,7 +88,7 @@ log=Connexion
 application=Application
 availablePermissions=Permissions Disponibles
 grantedPermissions=Permissions accord\u00e9es
-grantedPersonalInfo=Informations personnels accord\u00e9es
+grantedPersonalInfo=Informations personnelles accord\u00e9es
 additionalGrants=Droits additionnels
 action=Action
 inResource=dans
@@ -99,7 +99,7 @@ revoke=R\u00e9voquer un droit
 configureAuthenticators=Authentifications configur\u00e9es.
 mobile=T\u00e9l\u00e9phone mobile
 totpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
-totpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
+totpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9.
 totpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
 
 missingUsernameMessage=Veuillez entrer votre nom d''utilisateur.
@@ -140,17 +140,18 @@ identityProviderRemovedMessage=Le fournisseur d''identit\u00e9 a \u00e9t\u00e9 s
 accountDisabledMessage=Ce compte est d\u00e9sactiv\u00e9, veuillez contacter votre administrateur.
 
 accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, veuillez contacter votre administrateur ou r\u00e9essayez plus tard..
-invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}.
-invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.
-invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s).
-invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.
-invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
-invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur.
-invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle.
-invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
+invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
+invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
+invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
+invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
+invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
+invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas \u00eatre identique au nom d''utilisateur.
+invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l''expression rationnelle.
+invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
 
 locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
 locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties
index da7e9d0..2aa902a 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties
@@ -125,4 +125,4 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
index a569cfc..37c0106 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
@@ -147,4 +147,4 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (BR)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
index 87640e0..295a8ce 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
@@ -22,7 +22,7 @@ registrationEmailAsUsername=de Email as username
 registrationEmailAsUsername.tooltip=de If enabled then username field is hidden from registration form and email is used as username for new user.
 editUsernameAllowed=de Edit username
 editUsernameAllowed.tooltip=de If enabled, the username field is editable, readonly otherwise.
-resetPasswordAllowed=de Forget password
+resetPasswordAllowed=de Forgot password
 resetPasswordAllowed.tooltip=de Show a link on login page for user to click on when they have forgotten their credentials.
 rememberMe=de Remember Me
 rememberMe.tooltip=de Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index f13d6e8..ebf3a83 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -22,7 +22,7 @@ registrationEmailAsUsername=Email as username
 registrationEmailAsUsername.tooltip=If enabled then username field is hidden from registration form and email is used as username for new user.
 editUsernameAllowed=Edit username
 editUsernameAllowed.tooltip=If enabled, the username field is editable, readonly otherwise.
-resetPasswordAllowed=Forget password
+resetPasswordAllowed=Forgot password
 resetPasswordAllowed.tooltip=Show a link on login page for user to click on when they have forgotten their credentials.
 rememberMe=Remember Me
 rememberMe.tooltip=Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
@@ -374,6 +374,7 @@ table-of-identity-providers=Table of identity providers
 add-provider.placeholder=Add provider...
 provider=Provider
 gui-order=GUI order
+first-broker-login-flow=First Login Flow
 redirect-uri=Redirect URI
 redirect-uri.tooltip=The redirect uri to use when configuring the identity provider.
 alias=Alias
@@ -393,6 +394,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t
 trust-email=Trust Email
 trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
 gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page).
+first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider.
 openid-connect-config=OpenID Connect Config
 openid-connect-config.tooltip=OIDC SP and external IDP configuration.
 authorization-url=Authorization URL
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
new file mode 100644
index 0000000..170720a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
@@ -0,0 +1,466 @@
+# Common messages
+enabled=Habilitado
+name=Nombre
+save=Guardar
+cancel=Cancelar
+onText=SI
+offText=NO
+client=Cliente
+clients=Clientes
+clear=Limpiar
+selectOne=Selecciona uno...
+
+true=S\u00ED
+false=No
+
+
+# Realm settings
+realm-detail.enabled.tooltip=Los usuarios y clientes solo pueden acceder a un dominio si est\u00E1 habilitado
+registrationAllowed=Registro de usuario
+registrationAllowed.tooltip=Habilitar/deshabilitar la p\u00E1gina de registro. Un enlace para el registro se motrar\u00E1 tambi\u00E9n en la p\u00E1gina de inicio de sesi\u00F3n.
+registrationEmailAsUsername=Email como nombre de usuario
+registrationEmailAsUsername.tooltip=Si est\u00E1 habilitado el nombre de usuario queda oculto del formulario de registro y el email se usa como nombre de usuario para los nuevos usuarios.
+editUsernameAllowed=Editar nombre de usuario
+editUsernameAllowed.tooltip=Si est\u00E1 habilitado, el nombre de usuario es editable, en otro caso es de solo lectura.
+resetPasswordAllowed=Olvido contrase\u00F1a
+resetPasswordAllowed.tooltip=Muestra un enlace en la p\u00E1gina de inicio de sesi\u00F3n para que el usuario haga clic cuando ha olvidado sus credenciales.
+rememberMe=Seguir conectado
+rememberMe.tooltip=Muestra la casilla de selecci\u00F3n en la p\u00E1gina de inicio de sesi\u00F3n para permitir al usuario permanecer conectado entre reinicios del navegador hasta que la sesi\u00F3n expire.
+verifyEmail=Verificar email
+verifyEmail.tooltip=Forzar al usuario a verificar su direcci\u00F3n de email la primera vez que inicie sesi\u00F3n.
+sslRequired=Solicitar SSL
+sslRequired.option.all=todas las peticiones
+sslRequired.option.external=peticiones externas
+sslRequired.option.none=ninguna
+sslRequired.tooltip=\u00BFEs HTTP obligatorio? 'ninguna' significa que HTTPS no es obligatorio para ninguna direcic\u00F3n IP de cliente, 'peticiones externas' indica que localhost y las direcciones IP privadas pueden acceder sin HTTPS, 'todas las peticiones' significa que HTTPS es obligatorio para todas las direcciones IP.
+publicKey=Clave p\u00FAblica
+gen-new-keys=Generar nuevas claves
+certificate=Certificado
+host=Host
+smtp-host=Host SMTP
+port=Puerto
+smtp-port=Puerto SMTP (por defecto 25)
+from=De
+sender-email-addr=Email del emisor
+enable-ssl=Habilitar SSL
+enable-start-tls=Habilitar StartTLS
+enable-auth=Habilitar autenticaci\u00F3n
+username=Usuario
+login-username=Usuario
+password=Contrase\u00F1a
+login-password=Contrase\u00F1a
+login-theme=Tema de inicio de sesi\u00F3n
+select-one=Selecciona uno...
+login-theme.tooltip=Selecciona el tema para las p\u00E1gina de inicio de sesi\u00F3n, TOTP, permisos, registro y recordatorio de contrase\u00F1a.
+account-theme=Tema de cuenta
+account-theme.tooltip=Selecciona el tema para las p\u00E1ginas de gesti\u00F3n de la cuenta de usuario.
+admin-console-theme=Tema de consola de administraci\u00F3n
+select-theme-admin-console=Selecciona el tema para la consola de administraci\u00F3n.
+email-theme=Tema de email
+select-theme-email=Selecciona el tema para los emails que son enviados por el servidor.
+i18n-enabled=Internacionalizaci\u00F3n activa
+supported-locales=Idiomas soportados
+supported-locales.placeholder=Indica el idioma y pulsa Intro
+default-locale=Idioma por defecto
+realm-cache-enabled=Cach\u00E9 de dominio habilitada
+realm-cache-enabled.tooltip=Activar/desactivar la cach\u00E9 para el dominio, cliente y datos de roles.
+user-cache-enabled=Cach\u00E9 de usuario habilitada
+user-cache-enabled.tooltip=Habilitar/deshabilitar la cach\u00E9 de usuarios y de asignaciones de usuarios a roles.
+revoke-refresh-token=Revocar el token de actualizaci\u00F3n
+revoke-refresh-token.tooltip=Si est\u00E1 activado los tokens de actualizaci\u00F3n solo pueden usarse una vez. En otro caso los tokens de actualizaci\u00F3n no se revocan cuando se utilizan y pueden ser usado m\u00FAltiples veces.
+sso-session-idle=Sesiones SSO inactivas
+seconds=Segundos
+minutes=Minutos
+hours=Horas
+days=D\u00EDas
+sso-session-max=Tiempo m\u00E1ximo sesi\u00F3n SSO
+sso-session-idle.tooltip=Tiempo m\u00E1ximo que una sesi\u00F3n puede estar inactiva antes de que expire. Los tokens y sesiones de navegador son invalidadas cuando la sesi\u00F3n expira.
+sso-session-max.tooltip=Tiempo m\u00E1ximo antes de que una sesi\u00F3n expire. Los tokesn y sesiones de navegador son invalidados cuando una sesi\u00F3n expira.
+offline-session-idle=Inactividad de sesi\u00F3n sin conexi\u00F3n
+offline-session-idle.tooltip=Tiempo m\u00E1ximo inactivo de una sesi\u00F3n sin conexi\u00F3n antes de que expire. Necesitas usar un token sin conexi\u00F3n para refrescar al menos una vez dentro de este periodo, en otro caso la sesi\u00F3n sin conexi\u00F3n expirar\u00E1.
+access-token-lifespan=Duraci\u00F3n del token de acceso
+access-token-lifespan.tooltip=Tiempo m\u00E1ximo antes de que un token de acceso expire. Se recomiena que esta valor sea corto en relaci\u00F3n al tiempo m\u00E1ximo de SSO
+client-login-timeout=Tiempo m\u00E1ximo de autenticaci\u00F3n
+client-login-timeout.tooltip=Tiempo m\u00E1ximo que un cliente tien para finalizar el protocolo de obtenci\u00F3n del token de acceso. Deber\u00EDa ser normalmente del orden de 1 minuto.
+login-timeout=Tiempo m\u00E1ximo de desconexi\u00F3n
+login-timeout.tooltip=Tiempo m\u00E1xmo que un usuario tiene para completar el inicio de sesi\u00F3n. Se recomienda que sea relativamente alto. 30 minutos o m\u00E1s.
+login-action-timeout=Tiempo m\u00E1ximo de acci\u00F3n en el inicio de sesi\u00F3n
+login-action-timeout.tooltip=Tiempo m\u00E1ximo que un usuario tiene para completar acciones relacionadas con el inicio de sesi\u00F3n, como la actualizaci\u00F3n de contrase\u00F1a o configuraci\u00F3n de TOTP. Es recomendado que sea relativamente alto. 5 minutos o m\u00E1s.
+headers=Cabeceras
+brute-force-detection=Detecci\u00F3n de ataques por fuerza bruta
+x-frame-options=X-Frame-Options
+click-label-for-info=Haz clic en el enlace de la etiqueta para obtener m\u00E1s informaci\u00F3n. El valor por defecto evita que las p\u00E1ginas sean incluidaos desde iframes externos.
+content-sec-policy=Content-Security-Policy
+max-login-failures=N\u00FAmero m\u00E1ximo de fallos de inicios de sesi\u00F3n
+max-login-failures.tooltip=Indica cuantos fallos se permiten antes de que se dispare una espera.
+wait-increment=Incremento de espera
+wait-increment.tooltip=Cuando se ha alcanzado el umbral de fallo, \u00BFcuanto tiempo debe estar un usuario bloqueado?
+quick-login-check-millis=Tiempo en milisegundos entre inicios de sesi\u00F3n r\u00E1pidos
+quick-login-check-millis.tooltip=Si ocurren errores de forma concurrente y muy r\u00E1pida, bloquear al usuario.
+min-quick-login-wait=Tiempo m\u00EDnimo entre fallos de conexi\u00F3n r\u00E1pidos
+min-quick-login-wait.tooltip=Cuanto tiempo se debe esperar tras un fallo en un intento r\u00E1pido de identificaci\u00F3n
+max-wait=Espera m\u00E1xima
+max-wait.tooltip=Tiempo m\u00E1ximo que un usuario quedar\u00E1 bloqueado.
+failure-reset-time=Reinicio del contador de errores
+failure-reset-time.tooltip=\u00BFCuando se debe reiniciar el contador de errores?
+realm-tab-login=Inicio de sesi\u00F3n
+realm-tab-keys=Claves
+realm-tab-email=Email
+realm-tab-themes=Temas
+realm-tab-cache=Cache
+realm-tab-tokens=Tokens
+realm-tab-security-defenses=Defensas de seguridad
+realm-tab-general=General
+add-realm=A\u00F1adir dominio
+
+#Session settings
+realm-sessions=Sesiones de dominio
+revocation=Revocaci\u00F3n
+logout-all=Desconectar todo
+active-sessions=Sesiones activas
+sessions=Sesiones
+not-before=No antes de
+not-before.tooltip=Revocar cualquier token emitido antes de esta fecha.
+set-to-now=Fijar a ahora
+push=Push
+push.tooltip=Para cada cliente que tiene una URL de administraci\u00F3n, notificarlos the las nuevas pol\u00EDticas de revocaci\u00F3n.
+
+#Protocol Mapper
+usermodel.prop.label=Propiedad
+usermodel.prop.tooltip=Nombre del m\u00E9todo de propiedad in la interfaz UserModel. Por ejemplo, un valor de 'email' referenciar\u00EDa al m\u00E9todo UserModel.getEmail().
+usermodel.attr.label=Atributo de usuario
+usermodel.attr.tooltip=Nombre del atributo de usuario almacenado que es el nombre del atributo dentro del map UserModel.attribute.
+userSession.modelNote.label=Nota sesi\u00F3n usuario
+userSession.modelNote.tooltip=Nombre de la nota almacenada en la sesi\u00F3n de usuario dentro del mapa UserSessionModel.note
+multivalued.label=Valores m\u00FAltiples
+multivalued.tooltip=Indica si el atributo soporta m\u00FAltiples valores. Si est\u00E1 habilitado, la lista de todos los valores de este atributo se fijar\u00E1 como reclamaci\u00F3n. Si est\u00E1 deshabilitado, solo el primer valor ser\u00E1 fijado como reclamaci\u00F3n.
+selectRole.label=Selecciona rol
+selectRole.tooltip=Introduce el rol en la caja de texto de la izquierda, o haz clic en este bot\u00F3n para navegar y buscar el rol que quieres.
+tokenClaimName.label=Nombre de reclamo del token
+tokenClaimName.tooltip=Nombre del reclamo a insertar en el token. Puede ser un nombre completo como 'address.street'. En este caso, se crear\u00E1 un objeto JSON anidado.
+jsonType.label=Tipo JSON de reclamaci\u00F3n
+jsonType.tooltip=El tipo de JSON que deber\u00EDa ser usado para rellenar la petici\u00F3n de JSON en el token. long, int, boolean y String son valores v\u00E1lidos
+includeInIdToken.label=A\u00F1adir al token de ID
+includeInAccessToken.label=A\u00F1adir al token de acceso
+includeInAccessToken.tooltip=\u00BFDeber\u00EDa a\u00F1adirse la identidad reclamada al token de acceso?
+
+
+# client details
+clients.tooltip=Los clientes son aplicaciones de navegador de confianza y servicios web de un dominio. Estos clientes pueden solicitar un inicio de sesi\u00F3n. Tambi\u00E9n puedes definir roles espec\u00EDficos de cliente.
+search.placeholder=Buscar...
+create=Crear
+import=Importar
+client-id=ID Cliente
+base-url=URL Base
+actions=Acciones
+not-defined=No definido
+edit=Editar
+delete=Borrar
+no-results=Sin resultados
+no-clients-available=No hay clientes disponibles
+add-client=A\u00F1adir Cliente
+select-file=Selecciona archivo
+view-details=Ver detalles
+clear-import=Limpiar importaci\u00F3n
+client-id.tooltip=Indica el identificador (ID) referenciado en URIs y tokens. Por ejemplo 'my-client'
+client.name.tooltip=Indica el nombre visible del cliente. Por ejemplo 'My Client'. Tambi\u00E9n soporta claves para vallores localizados. Por ejemplo: ${my_client}
+client.enabled.tooltip=Los clientes deshabilitados no pueden iniciar una identificaci\u00F3n u obtener c\u00F3digos de acceso.
+consent-required=Consentimiento necesario
+consent-required.tooltip=Si est\u00E1 habilitado, los usuarios tienen que consentir el acceso del cliente.
+direct-grants-only=Solo permisos directos
+direct-grants-only.tooltip=Cuando est\u00E1 habilitado, el cliente solo puede obtener permisos de la API REST.
+client-protocol=Protocolo del Cliente
+client-protocol.tooltip='OpenID connect' permite a los clientes verificar la identidad del usuario final basado en la autenticaci\u00F3n realizada por un servidor de autorizaci\u00F3n. 'SAML' habilita la autenticaci\u00F3n y autorizaci\u00F3n de escenarios basados en web incluyendo cross-domain y single sign-on (SSO) y utiliza tokdne de seguridad que contienen afirmaciones para pasar informaci\u00F3n.
+access-type=Tipo de acceso
+access-type.tooltip=Los clientes 'Confidential' necesitan un secreto para iniciar el protocolo de identificaci\u00F3n. Los clientes 'Public' no requieren un secreto. Los clientes 'Bearer-only' son servicios web que nunca inician un login.
+service-accounts-enabled=Cuentas de servicio habilitadas
+service-accounts-enabled.tooltip=Permitir autenticar este cliente contra Keycloak y recivir un token de acceso dedicado para este cliente.
+include-authnstatement=Incluir AuthnStatement
+include-authnstatement.tooltip=null
+sign-documents=Firmar documentos
+sign-documents.tooltip=\u00BFDeber\u00EDa el dominio firmar los documentos SAML?
+sign-assertions=Firmar aserciones
+sign-assertions.tooltip=\u00BFDeber\u00EDan firmarse las aserciones en documentos SAML? Este ajuste no es necesario si el documento ya est\u00E1 siendo firmado.
+signature-algorithm=Algoritmo de firma
+signature-algorithm.tooltip=El algoritmo de firma usado para firmar los documentos.
+canonicalization-method=M\u00E9todo de canonicalizaci\u00F3n
+canonicalization-method.tooltip=M\u00E9todo de canonicalizaci\u00F3n para las firmas XML
+encrypt-assertions=Cifrar afirmaciones
+encrypt-assertions.tooltip=\u00BFDeber\u00EDan cifrarse las afirmaciones SAML con la clave p\u00FAblica del cliente usando AES?
+client-signature-required=Firma de Cliente requerida
+client-signature-required.tooltip=\u00BFFirmar\u00E1 el cliente sus peticiones y respuestas SAML? \u00BFY deber\u00EDan ser validadas?
+force-post-binding=Forzar enlaces POST
+force-post-binding.tooltip=Usar siempre POST para las respuestas
+front-channel-logout=Desonexi\u00F3n en primer plano (Front Channel)
+front-channel-logout.tooltip=Cuando est\u00E1 activado, la desconexi\u00F3n require una redirecci\u00F3n del navegador hacia el cliente. Cuando no est\u00E1 activado, el servidor realiza una invovaci\u00F3n de desconexi\u00F3n en segundo plano.
+force-name-id-format=Forzar formato NameID
+force-name-id-format.tooltip=Ignorar la petici\u00F3n de sujeto NameID y usar la configurada en la consola de administraci\u00F3n.
+name-id-format=Formato de NameID
+name-id-format.tooltip=El formato de NameID que se usar\u00E1 para el t\u00EDtulo
+root-url=URL ra\u00EDz
+root-url.tooltip=URL ra\u00EDz a\u00F1adida a las URLs relativas
+valid-redirect-uris=URIs de redirecci\u00F3n v\u00E1lidas
+valid-redirect-uris.tooltip=Patr\u00F3n de URI v\u00E1lida para la cual un navegador puede solicitar la redirecci\u00F3n tras un inicio o cierre de sesi\u00F3n completado. Se permiten comodines simples p.ej. 'http://example.com/*'. Tambi\u00E9n se pueden indicar rutas relativas p.ej. '/my/relative/path/*'. Las rutas relativas generar\u00E1n una URI de redirecci\u00F3n usando el host y puerto de la petici\u00F3n. Para SAML, se deben fijar patrones de URI v\u00E1lidos si quieres confiar en la URL del servicio del consumidor indicada en la petici\u00F3n de inicio de sesi\u00F3n.
+base-url.tooltip=URL por defecto para usar cuando el servidor de autorizaci\u00F3n necesita redirigir o enviar de vuelta al cliente.
+admin-url=URL de administraci\u00F3n
+admin-url.tooltip=URL a la interfaz de administraci\u00F3n del cliente. Fija este valor si el cliente soporta el adaptador de REST. Esta API REST permite al servidor de autenticaci\u00F3n enviar al cliente pol\u00EDticas de revocaci\u00F3n y otras tareas administrativas. Normalment se fija a la URL base del cliente.
+master-saml-processing-url=URL principal de procesamiento SAML
+master-saml-processing-url.tooltip=Si est\u00E1 configurada, esta URL se usar\u00E1 para cada enlace al proveedor del servicio del consumidor de aserciones y servicios de desconexi\u00F3n \u00FAnicos. Puede ser sobreescrito de forma individual para cada enlace y servicio en el punto final de configuraci\u00F3n fina de SAML.
+idp-sso-url-ref=Nombre de la URL de un SSO iniciado por el IDP
+idp-sso-url-ref.tooltip=Nombre del fragmento de la URL para referenciar al cliente cuando quieres un SSO iniciado por el IDP. Dejanto esto vac\u00EDo deshabilita los SSO iniciados por el IDP. La URL referenciada desde el navegador ser\u00E1: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-relay-state=Estado de retransmisi\u00F3n de un SSO iniciado por el IDP
+idp-sso-relay-state.tooltip=Estado de retransmisi\u00F3n que quiees enviar con una petici\u00F3n SAML cuando se inicia un SSO inidicado por el IDP
+web-origins=Origenes web
+web-origins.tooltip=Origenes CORS permitidos. Para permitir todos los or\u00EDgenes de URIs de redirecci\u00F3n v\u00E1lidas a\u00F1ade '+'. Para permitir todos los or\u00EDgenes a\u00F1ade '*'.
+fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration
+fine-saml-endpoint-conf.tooltip=Expande esta secci\u00F3n para configurar las URL exactas para Assertion Consumer y Single Logout Service.
+assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL
+assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses).  You can leave this blank if you do not have a URL for this binding.
+assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL
+assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL
+logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n
+logout-service-binding-post-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
+logout-service-redir-binding-url=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n
+logout-service-redir-binding-url.tooltip=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
+
+# client import
+import-client=Importar Cliente
+format-option=Formato
+select-format=Selecciona un formato
+import-file=Archivo de Importaci\u00F3n
+
+# client tabs
+settings=Ajustes
+credentials=Credenciales
+saml-keys=Claves SAML
+roles=Roles
+mappers=Asignadores
+mappers.tootip=Los asignadores de protocolos realizan transformaciones en tokens y documentos. Pueden hacer cosas como asignar datos de usuario en peticiones de protocolo, o simplemente transformar cualquier petici\u00F3n entre el cliente y el servidor de autenticaci\u00F3n.
+scope=\u00C1mbito
+scope.tooltip=Las asignaciones de \u00E1mbito te permiten restringir que asignaciones de roles de usuario se incluyen en el token de acceso solicitado por el cliente.
+sessions.tooltip=Ver sesiones activas para este cliente. Permite ver qu\u00E9 usuarios est\u00E1n activos y cuando se identificaron.
+offline-access=Acceso sin conexi\u00F3n
+offline-access.tooltip=Ver sesiones sin conexi\u00F3n para este cliente. Te permite ver que usuarios han solicitado tokens sin conexi\u00F3n y cuando los solicitaron. Para revocar todos los tokens del cliente, accede a la pesta\u00F1a de Revocaci\u00F3n y fija el valor \"No antes de\" a \"now\".
+clustering=Clustering
+installation=Instalaci\u00F3n
+installation.tooltip=Herramienta de ayuda para generar la configuraci\u00F3n de varios formatos de adaptadores de cliente que puedes descargar o copiar y pegar para configurar tus clientes.
+service-account-roles=Roles de cuenta de servicio
+service-account-roles.tooltip=Permitir autenticar asignaciones de rol para la cuenta de servicio dedicada a este cliente.
+
+# client credentials
+client-authenticator=Cliente autenticador
+client-authenticator.tooltip=Cliente autenticador usado para autenticar este cliente contra el servidor Keycloak
+certificate.tooltip=Certificado de clinete para validar los JWT emitidos por este cliente y firmados con la clave privada del cliente de tu almac\u00E9n de claves.
+no-client-certificate-configured=No se ha configurado el certificado de cliente
+gen-new-keys-and-cert=Genearr nuevas claves y certificado
+import-certificate=Importar Certificado
+gen-client-private-key=Generar clave privada de cliente
+generate-private-key=Generar clave privada
+archive-format=Formato de Archivo
+archive-format.tooltip=Formato de archivo Java keystore o PKCS12
+key-alias=Alias de clave
+key-alias.tooltip=Alias del archivo de tu clave privada y certificado.
+key-password=Contrase\u00F1a de la clave
+key-password.tooltip=Contrase\u00F1a para acceder a la clave privada contenida en el archivo
+store-password=Contrase\u00F1a del almac\u00E9n
+store-password.tooltip=Contrase\u00F1a para acceder al archivo
+generate-and-download=Generar y descargar
+client-certificate-import=Importaci\u00F3n de certificado de cliente
+import-client-certificate=Importar Certificado de Cliente
+jwt-import.key-alias.tooltip=Alias del archivo de tu certificado.
+secret=Secreto
+regenerate-secret=Regenerar secreto
+add-role=A\u00F1adir rol
+role-name=Nombre de rol
+composite=Compuesto
+description=Descripci\u00F3n
+no-client-roles-available=No hay roles de cliente disponibles
+scope-param-required=Par\u00E1metro de \u00E1mbito obligatorio
+scope-param-required.tooltip=Este rol solo ser\u00E1 concedido si el par\u00E1metro de \u00E1mbito con el nombre del rol es usado durante la petici\u00F3n de autorizaci\u00F3n/obtenci\u00F3n de token.
+composite-roles=Roles compuestos
+composite-roles.tooltip=Cuanto este rol es asignado/desasignado a un usuario cualquier rol asociado con \u00E9l ser\u00E1 asignado/desasignado de forma impl\u00EDcita.
+realm-roles=Roles de dominio
+available-roles=Roles Disponibles
+add-selected=A\u00F1adir seleccionado
+associated-roles=Roles Asociados
+composite.associated-realm-roles.tooltip=Roles a nivel de dominio asociados con este rol compuesto.
+composite.available-realm-roles.tooltip=Roles a nivel de dominio disponibles en este rol compuesto.
+remove-selected=Borrar seleccionados
+client-roles=Roles de Cliente
+select-client-to-view-roles=Selecciona el cliente para ver sus roles
+available-roles.tooltip=Roles de este cliente que puedes asociar a este rol compuesto.
+client.associated-roles.tooltip=Roles de cliente asociados con este rol compuesto.
+add-builtin=A\u00F1adir Builtin
+category=Categor\u00EDa
+type=Tipo
+no-mappers-available=No hay asignadores disponibles
+add-builtin-protocol-mappers=A\u00F1adir Builtin Protocol Mappers
+add-builtin-protocol-mapper=A\u00F1adir Builtin Protocol Mapper
+scope-mappings=Asignaciones de \u00E1mbito
+full-scope-allowed=Permitir todos los \u00E1mbitos
+full-scope-allowed.tooltip=Permite deshabilitar todas las restricciones.
+scope.available-roles.tooltip=Roles de dominio que pueden ser asignados al \u00E1mbito
+assigned-roles=Roles Asignados
+assigned-roles.tooltip=Roles a nivel de dominio asignados a este \u00E1mbito.
+effective-roles=Roles Efectivos
+realm.effective-roles.tooltip=Roles de dominio asignados que pueden haber sido heredados de un rol compuesto.
+select-client-roles.tooltip=Selecciona el cliente para ver sus roles
+assign.available-roles.tooltip=Roles de clientes disponibles para ser asignados.
+client.assigned-roles.tooltip=Roles de cliente asignados
+client.effective-roles.tooltip=Roles de cliente asignados que pueden haber sido heredados desde un rol compuesto.
+basic-configuration=Configuraci\u00F3n b\u00E1sica
+node-reregistration-timeout=Tiempo de espera de re-registro de nodo
+node-reregistration-timeout.tooltip=Indica el m\u00E1ximo intervalo de tiempo para que los nodos del cluster registrados se vuelvan a registrar. Si el nodo del cluster no env\u00EDa una petici\u00F3n de re-registro a Keycloak dentro de este intervalo, ser\u00E1 desregistrado de Keycloak
+registered-cluster-nodes=Registrar nodos de cluster
+register-node-manually=Registrar nodo manualmente
+test-cluster-availability=Probar disponibilidad del cluster
+last-registration=\u00DAltimo registro
+node-host=Host del nodo
+no-registered-cluster-nodes=No hay nodos de cluster registrados disponibles
+cluster-nodes=Nodos de cluster
+add-node=A\u00F1adir Nodo
+active-sessions.tooltip=N\u00FAmero total de sesiones activas para este cliente.
+show-sessions=Mostrar sesiones
+show-sessions.tooltip=Advertencia, esta es una operaci\u00F3n potencialmente costosa dependiendo del n\u00FAmero de sesiones activas.
+user=Usuario
+from-ip=Desde IP
+session-start=Inicio de sesi\u00F3n
+first-page=Primera p\u00E1gina
+previous-page=P\u00E1gina Anterior
+next-page=P\u00E1gina siguiente
+client-revoke.not-before.tooltip=Revocar todos los tokens emitidos antes de esta fecha para este cliente.
+client-revoke.push.tooltip=Si la URL de administraci\u00F3n est\u00E1 configurada para este cliente, env\u00EDa esta pol\u00EDtica a este cliente.
+select-a-format=Selecciona un formato
+download=Descargar
+offline-tokens=Tokens sin conexi\u00F3n
+offline-tokens.tooltip=N\u00FAmero total de tokens sin conexi\u00F3n de este cliente.
+show-offline-tokens=Mostrar tokens sin conexi\u00F3n
+show-offline-tokens.tooltip=Advertencia, esta es una operaci\u00F3n potencialmente costosa dependiendo del n\u00FAmero de tokens sin conexi\u00F3n.
+token-issued=Token expedido
+last-access=\u00DAltimo Acceso
+last-refresh=\u00DAltima actualizaci\u00F3n
+key-export=Exportar clave
+key-import=Importar clave
+export-saml-key=Exportar clave SAML
+import-saml-key=Importar clave SAML
+realm-certificate-alias=Alias del certificado del dominio
+realm-certificate-alias.tooltip=El certificado del dominio es almacenado en archivo. Este es el alias al mismo.
+signing-key=Clave de firma
+saml-signing-key=Clave de firma SAML.
+private-key=Clave Privada
+generate-new-keys=Generar nuevas claves
+export=Exportar
+encryption-key=Clave de cifrado
+saml-encryption-key.tooltip=Clave de cifrado de SAML
+service-accounts=Cuentas de servicio
+service-account.available-roles.tooltip=Roles de dominio que pueden ser asignados a la cuenta del servicio.
+service-account.assigned-roles.tooltip=Roles de dominio asignados a la cuenta del servicio.
+service-account-is-not-enabled-for=La cuenta del servicio no est\u00E1 habilitada para {{client}}
+create-protocol-mappers=Crear asignadores de protocolo
+create-protocol-mapper=Crear asignador de protocolo
+protocol=Protocolo
+protocol.tooltip=Protocolo.
+id=ID
+mapper.name.tooltip=Nombre del asignador.
+mapper.consent-required.tooltip=Cuando se concede acceso temporal, \u00BFes necesario el consentimiento del usuario para proporcinar estos datos al cliente cliente?
+consent-text=Texto del consentimiento
+consent-text.tooltip=Texto para mostrar en la p\u00E1gina de consentimiento.
+mapper-type=Tipo de asignador
+
+# realm identity providers
+identity-providers=Proveedores de identidad
+table-of-identity-providers=Tabla de proveedores de identidad
+add-provider.placeholder=A\u00F1adir proveedor...
+provider=Proveedor
+gui-order=Orden en la interfaz gr\u00E1fica (GUI)
+redirect-uri=URI de redirecci\u00F3n
+redirect-uri.tooltip=La URI de redirecci\u00F3n usada para configurar el proveedor de identidad.
+alias=Alias
+identity-provider.alias.tooltip=El alias que identifica de forma \u00FAnica un proveedor de identidad, se usa tambi\u00E9n para construir la URI de redirecci\u00F3n.
+identity-provider.enabled.tooltip=Habilita/deshabilita este proveedor de identidad.
+authenticate-by-default=Autenticar por defecto
+identity-provider.authenticate-by-default.tooltip=Indica si este proveedor deber\u00EDa ser probado por defecto para autenticacaci\u00F3n incluso antes de mostrar la p\u00E1gina de inicio de sesi\u00F3n.
+store-tokens=Almacenar tokens
+identity-provider.store-tokens.tooltip=Hablitar/deshabilitar si los tokens deben ser almacenados despu\u00E9s de autenticar a los usuarios.
+stored-tokens-readable=Tokens almacenados legibles
+identity-provider.stored-tokens-readable.tooltip=Habilitar/deshabilitar is los nuevos usuarios pueden leear los tokens almacenados. Esto asigna el rol 'broker.read-token'.
+update-profile-on-first-login=Actualizar perfil en el primer inicio de sesi\u00F3n
+on=Activado
+on-missing-info=Si falta informaci\u00F3n
+off=Desactivado
+update-profile-on-first-login.tooltip=Define condiciones bajos las cuales un usuario tiene que actualizar su perfil durante el primer inicio de sesi\u00F3n.
+trust-email=Confiar en el email
+trust-email.tooltip=Si est\u00E1 habilitado, el email recibido de este proveedor no se verificar\u00E1 aunque la verificaci\u00F3n est\u00E9 habilitada para el dominio.
+gui-order.tooltip=N\u00FAmero que define el orden del proveedor en la interfaz gr\u00E1fica (GUI) (ej. en la p\u00E1gina de inicio de sesi\u00F3n)
+openid-connect-config=Configuraci\u00F3n de OpenID Connect
+openid-connect-config.tooltip=Configuraci\u00F3n de OIDC SP e IDP externos
+authorization-url=URL de autorizaci\u00F3n
+authorization-url.tooltip=La URL de autorizaci\u00F3n.
+token-url=Token URL
+token-url.tooltip=La URL del token.
+logout-url=URL de desconexi\u00F3n
+identity-provider.logout-url.tooltip=Punto de cierre de sesi\u00F3n para usar en la desconexi\u00F3n de usuarios desde un proveedor de identidad (IDP) externo.
+backchannel-logout=Backchannel Logout
+backchannel-logout.tooltip=Does the external IDP support backchannel logout?
+user-info-url=URL de informaci\u00F3n de usuario
+user-info-url.tooltip=.La URL de informaci\u00F3n de usuario. Opcional
+identity-provider.client-id.tooltip=El cliente o identificador de cliente registrado en el proveedor de identidad.
+client-secret=Secreto de Cliente
+show-secret=Mostrar secreto
+hide-secret=Ocultar secreto
+client-secret.tooltip=El cliente o el secreto de cliente registrado en el proveedor de identidad.
+issuer=Emisor
+issuer.tooltip=El identificador del emisor para el emisor de la respuesta. Si no se indica, no se realizar\u00E1 ninguna validaci\u00F3n.
+default-scopes=\u00C1mbitos por defecto
+identity-provider.default-scopes.tooltip=Los \u00E1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Puede ser una lista de \u00E1mbitos separados por espacios. El valor por defecto es 'openid'.
+prompt=Prompt
+unspecified.option=no especificado
+none.option=ninguno
+consent.option=consentimiento
+login.option=login
+select-account.option=select_account
+prompt.tooltip=Indica si el servidor de autorizaci\u00F3n solicita al usuario final para reautenticaci\u00F3n y consentimiento.
+validate-signatures=Validar firmas
+identity-provider.validate-signatures.tooltip=Habilitar/deshabilitar la validaci\u00F3n de firmas de proveedores de identidad (IDP) externos
+validating-public-key=Validando clave p\u00FAblica
+identity-provider.validating-public-key.tooltip=La clave p\u00FAblica en formato PEM que debe usarse para verificar las firmas de proveedores de identidad (IDP) externos.
+import-external-idp-config=Importar configuraci\u00F3n externa de IDP
+import-external-idp-config.tooltip=Te permite cargar metadatos de un proveedor de identidad (IDP) externo de un archivo de coniguraci\u00F3n o descargarlo desde una URL.
+import-from-url=Importar desde URL
+identity-provider.import-from-url.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) remoto.
+import-from-file=Importar desde archivo
+identity-provider.import-from-file.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) descargado.
+saml-config=Configuraci\u00F3n SAML
+identity-provider.saml-config.tooltip=Configurci\u00F3n de proveedor SAML e IDP externo
+single-signon-service-url=URL de servicio de conexi\u00F3n \u00FAnico (SSO)
+saml.single-signon-service-url.tooltip=La URL que debe ser usada para enviar peticiones de autenticaci\u00F3n (SAML AuthnRequest).
+single-logout-service-url=URL de servicio de desconexi\u00F3n \u00FAnico
+saml.single-logout-service-url.tooltip=La URL que debe usarse para enviar peticiones de desconexi\u00F3n.
+nameid-policy-format=Formato de pol\u00EDtica NameID
+nameid-policy-format.tooltip=Indica la referencia a la URI correspondiente a un formato de NameID. El valor por defecto es urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
+http-post-binding-response=HTTP-POST enlace de respuesta
+http-post-binding-response.tooltip=Indica si se reponde a las peticiones usando HTTP-POST. Si no est\u00E1 activado, se usa HTTP-REDIRECT. 
+http-post-binding-for-authn-request=HTTP-POST para AuthnRequest
+http-post-binding-for-authn-request.tooltip=Indica si AuthnRequest debe ser envianda usando HTTP-POST. Si no est\u00E1 activado se hace HTTP-REDIRECT.
+want-authn-requests-signed=Firmar AuthnRequests
+want-authn-requests-signed.tooltip=Indica si el proveedor de identidad espera recibir firmadas las AuthnRequest.
+force-authentication=Forzar autenticaci\u00F3n
+identity-provider.force-authentication.tooltip=Indica si el proveedor de identidad debe autenticar al presentar directamente las credenciales en lugar de depender de un contexto de seguridad previo.
+validate-signature=Validar firma
+saml.validate-signature.tooltip=Habilitar/deshabilitar la validaci\u00F3n de firma en respuestas SAML.
+validating-x509-certificate=Validando certificado X509
+validating-x509-certificate.tooltip=El certificado en formato PEM que debe usarse para comprobar las firmas.
+saml.import-from-url.tooltip=Importar metadatos desde un descriptor de entidad remoto de un IDP de SAML
+social.client-id.tooltip=El identificador del cliente registrado con el proveedor de identidad.
+social.client-secret.tooltip=El secreto del cliente registrado con el proveedor de identidad.
+social.default-scopes.tooltip=\u00C1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Ver la documentaci\u00F3n para los posibles valores, separador y valor por defecto.
+key=Clave
+stackoverflow.key.tooltip=La clave obtenida en el registro del cliente de Stack Overflow.
+
+realms=Dominios
+realm=Dominio
+
+identity-provider-mappers=Asignadores de proveedores de identidad (IDP)
+create-identity-provider-mapper=Crear asignador de proveedor de identidad (IDP)
+add-identity-provider-mapper=A\u00F1adir asignador de proveedor de identidad
+client.description.tooltip=Indica la descripci\u00F3n del cliente. Por ejemplo 'My Client for TimeSheets'. Tambi\u00E9n soporta claves para valores localizzados. Por ejemplo: ${my_client_description}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
new file mode 100644
index 0000000..b5b7ca8
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
@@ -0,0 +1,8 @@
+invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
+invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
+invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
+invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
+invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular.
+invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index b4fe668..2fd5382 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -199,6 +199,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 providerFactory : function(IdentityProviderFactoryLoader) {
                     return {};
+                },
+                authFlows : function(AuthenticationFlowsLoader) {
+                    return {};
                 }
             },
             controller : 'RealmIdentityProviderCtrl'
@@ -217,6 +220,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 providerFactory : function(IdentityProviderFactoryLoader) {
                     return new IdentityProviderFactoryLoader();
+                },
+                authFlows : function(AuthenticationFlowsLoader) {
+                    return AuthenticationFlowsLoader();
                 }
             },
             controller : 'RealmIdentityProviderCtrl'
@@ -235,6 +241,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 providerFactory : function(IdentityProviderFactoryLoader) {
                     return IdentityProviderFactoryLoader();
+                },
+                authFlows : function(AuthenticationFlowsLoader) {
+                    return AuthenticationFlowsLoader();
                 }
             },
             controller : 'RealmIdentityProviderCtrl'
@@ -1406,12 +1415,15 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmOtpPolicyCtrl'
         })
-        .when('/realms/:realm/authentication/config/:provider/:config', {
+        .when('/realms/:realm/authentication/flows/:flow/config/:provider/:config', {
             templateUrl : resourceUrl + '/partials/authenticator-config.html',
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
+                flow : function(AuthenticationFlowLoader) {
+                    return AuthenticationFlowLoader();
+                },
                 configType : function(AuthenticationConfigDescriptionLoader) {
                     return AuthenticationConfigDescriptionLoader();
                 },
@@ -1421,12 +1433,15 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'AuthenticationConfigCtrl'
         })
-        .when('/create/authentication/:realm/execution/:executionId/provider/:provider', {
+        .when('/create/authentication/:realm/flows/:flow/execution/:executionId/provider/:provider', {
             templateUrl : resourceUrl + '/partials/authenticator-config.html',
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
+                flow : function(AuthenticationFlowLoader) {
+                    return AuthenticationFlowLoader();
+                },
                 configType : function(AuthenticationConfigDescriptionLoader) {
                     return AuthenticationConfigDescriptionLoader();
                 },
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 4fa1d8f..6cd5bea 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -337,7 +337,7 @@ module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serv
     $scope.supportedLocalesOptions = {
         'multiple' : true,
         'simple_tags' : true,
-        'tags' : ['en', 'de', 'pt-BR', 'it']
+        'tags' : ['en', 'de', 'pt-BR', 'it', 'es']
     };
 
     $scope.$watch('realm.supportedLocales', function(oldVal, newVal) {
@@ -594,20 +594,11 @@ module.controller('IdentityProviderTabCtrl', function(Dialog, $scope, Current, N
     };
 });
 
-module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications, Dialog) {
+module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, authFlows, $location, Notifications, Dialog) {
     console.log('RealmIdentityProviderCtrl');
 
     $scope.realm = angular.copy(realm);
 
-    $scope.initProvider = function() {
-        if (instance && instance.alias) {
-
-        } else {
-            $scope.identityProvider.updateProfileFirstLoginMode = "on";
-        }
-
-    };
-
     $scope.initSamlProvider = function() {
         $scope.nameIdFormats = [
             /*
@@ -658,7 +649,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
         } else {
             $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format;
             $scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1];
-            $scope.identityProvider.updateProfileFirstLoginMode = "off";
         }
     }
 
@@ -676,8 +666,8 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
         $scope.identityProvider.alias = providerFactory.id;
         $scope.identityProvider.providerId = providerFactory.id;
         $scope.identityProvider.enabled = true;
-        $scope.identityProvider.updateProfileFirstLoginMode = "off";
         $scope.identityProvider.authenticateByDefault = false;
+        $scope.identityProvider.firstBrokerLoginFlowAlias = 'first broker login';
         $scope.newIdentityProvider = true;
     }
 
@@ -696,6 +686,13 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
 
     $scope.configuredProviders = angular.copy(realm.identityProviders);
 
+    $scope.authFlows = [];
+    for (var i=0 ; i<authFlows.length ; i++) {
+        if (authFlows[i].providerId == 'basic-flow') {
+            $scope.authFlows.push(authFlows[i]);
+        }
+    }
+
     $scope.$watch(function() {
         return $location.path();
     }, function() {
@@ -1901,8 +1898,9 @@ module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredReq
 
 });
 
-module.controller('AuthenticationConfigCtrl', function($scope, realm, configType, config, AuthenticationConfig, Notifications, Dialog, $location) {
+module.controller('AuthenticationConfigCtrl', function($scope, realm, flow, configType, config, AuthenticationConfig, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.flow = flow;
     $scope.configType = configType;
     $scope.create = false;
     $scope.config = angular.copy(config);
@@ -1927,7 +1925,7 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType
         }, $scope.config, function() {
             $scope.changed = false;
             config = angular.copy($scope.config);
-            $location.url("/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + config.id);
+            $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + config.id);
             Notifications.success("Your changes have been saved.");
         });
     };
@@ -1946,15 +1944,16 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType
         Dialog.confirmDelete($scope.config.alias, 'config', function() {
             AuthenticationConfig.remove({ realm: realm.realm, config : $scope.config.id }, function() {
                 Notifications.success("The config has been deleted.");
-                $location.url("/realms/" + realm.realm + '/authentication/flows');
+                $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id);
             });
         });
     };
 
 });
 
-module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) {
+module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.flow = flow;
     $scope.create = true;
     $scope.config = { config: {}};
     $scope.configType = configType;
@@ -1972,7 +1971,7 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, conf
         }, $scope.config, function(data, headers) {
             var l = headers().location;
             var id = l.substring(l.lastIndexOf("/") + 1);
-            var url = "/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + id;
+            var url = "/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + id;
             console.log('redirect url: ' + url);
             $location.url(url);
             Notifications.success("Config has been created.");
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
index e055978..a77cf9f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
@@ -53,8 +53,8 @@
                         <li data-ng-hide="flow.builtIn"><a href="" ng-click="removeExecution(execution)">Delete</a></li>
                         <li data-ng-hide="flow.builtIn || !execution.authenticationFlow"><a href="" ng-click="addSubFlowExecution(execution)">Add Execution</a></li>
                         <li data-ng-hide="flow.builtIn || !execution.authenticationFlow"><a href="" ng-click="addSubFlow(execution)">Add Flow</a></li>
-                        <li data-ng-show="execution.configurable && execution.authenticationConfig == null"><a href="#/create/authentication/{{realm.realm}}/execution/{{execution.id}}/provider/{{execution.providerId}}">Config</a></li>
-                        <li data-ng-show="execution.configurable && execution.authenticationConfig != null"><a href="#/realms/{{realm.realm}}/authentication/config/{{execution.providerId}}/{{execution.authenticationConfig}}">Config</a></li>
+                        <li data-ng-show="execution.configurable && execution.authenticationConfig == null"><a href="#/create/authentication/{{realm.realm}}/flows/{{flow.id}}/execution/{{execution.id}}/provider/{{execution.providerId}}">Config</a></li>
+                        <li data-ng-show="execution.configurable && execution.authenticationConfig != null"><a href="#/realms/{{realm.realm}}/authentication/flows/{{flow.id}}/config/{{execution.providerId}}/{{execution.authenticationConfig}}">Config</a></li>
                     </ul>
                 </div>
             </td>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html
index 5856806..f587568 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html
@@ -2,6 +2,7 @@
 
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/authentication/flows">Authentication Flows</a></li>
+        <li><a href="#/realms/{{realm.realm}}/authentication/flows/{{flow.alias}}">{{flow.alias | capitalize}}</a></li>
         <li class="active" data-ng-show="create">Create Authenticator Config</li>
         <li class="active" data-ng-hide="create">{{config.alias}}</li>
     </ol>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
index 53c82d1..78eb532 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
@@ -11,7 +11,7 @@
                 <div>
                     <select class="form-control" id="provider"
                             ng-model="provider"
-                            ng-options="provider.id for provider in providers">
+                            ng-options="provider.displayName|capitalize for provider in providers">
                     </select>
                 </div>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html
index e884ef8..e29eef4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html
@@ -7,7 +7,7 @@
         <div class="form-group">
             <label class="col-md-2 control-label" for="alias">Alias </label>
             <div class="col-sm-6">
-                <input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus>
+                <input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus required>
             </div>
             <kc-tooltip>Specifies display name for the flow.</kc-tooltip>
         </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
index 85ad20b..bf06303 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
@@ -46,7 +46,7 @@
         <div class="form-group">
             <label class="col-md-2 control-label" for="lookAhead">Look ahead window</label>
             <div class="col-md-6">
-                <input class="form-control" type="text" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
+                <input class="form-control" type="number" required min="1" max="120" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
             </div>
             <kc-tooltip>How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?</kc-tooltip>
         </div>
@@ -62,7 +62,7 @@
         <div class="form-group" data-ng-show="realm.otpPolicyType == 'totp'">
             <label class="col-md-2 control-label" for="counter">OTP Token Period</label>
             <div class="col-md-6">
-                <input class="form-control" type="text" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
+                <input class="form-control" type="number" required min="1" max="120" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
             </div>
             <kc-tooltip>How many seconds should an OTP token be valid? Defaults to 30 seconds.</kc-tooltip>
         </div>
@@ -79,4 +79,4 @@
 </div>
 
 
-<kc-menu></kc-menu>
\ No newline at end of file
+<kc-menu></kc-menu>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index 170bfe6..4cbd12a 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -1,4 +1,4 @@
-<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" data-ng-init="initProvider()">
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
         <li>{{identityProvider.alias}}</li>
@@ -53,19 +53,6 @@
                 <kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
-                <div class="col-md-2">
-                		<div>
-                        <select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
-                            <option value="on">{{:: 'on' | translate}}</option>
-                            <option value="missing">{{:: 'on-missing-info' | translate}}</option>
-                            <option value="off">{{:: 'off' | translate}}</option>
-                        </select>
-                    </div>
-                </div>
-                <kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
-            </div>
-            <div class="form-group">
                 <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
                 <div class="col-md-6">
                     <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -79,6 +66,19 @@
                 </div>
                 <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="firstBrokerLoginFlowAlias"
+                                ng-model="identityProvider.firstBrokerLoginFlowAlias"
+                                ng-options="flow.alias as flow.alias for flow in authFlows"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
         <fieldset>
             <legend uncollapsed><span class="text">{{:: 'openid-connect-config' | translate}}</span> <kc-tooltip>{{:: 'openid-connect-config.tooltip' | translate}}</kc-tooltip></legend>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
index bb9726f..c8f6c37 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
@@ -53,19 +53,6 @@
                 <kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
-                <div class="col-md-2">
-                		<div>
-                        <select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
-                            <option value="on">{{:: 'on' | translate}}</option>
-                            <option value="missing">{{:: 'on-missing-info' | translate}}</option>
-                            <option value="off">{{:: 'off' | translate}}</option>
-                        </select>
-                    </div>
-                </div>
-                <kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
-            </div>
-            <div class="form-group">
                 <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
                 <div class="col-md-6">
                     <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -79,6 +66,19 @@
                 </div>
                 <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="firstBrokerLoginFlowAlias"
+                                ng-model="identityProvider.firstBrokerLoginFlowAlias"
+                                ng-options="flow.alias as flow.alias for flow in authFlows"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
         <fieldset>
             <legend uncollapsed><span class="text">{{:: 'saml-config' | translate}}</span> <kc-tooltip>{{:: 'identity-provider.saml-config.tooltip' | translate}}</kc-tooltip></legend>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
index 2f71d19..5897f99 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
@@ -1,4 +1,4 @@
-<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" data-ng-init="initProvider()">
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
         <li>{{identityProvider.alias}}</li>
@@ -64,19 +64,6 @@
                 <kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
-                <div class="col-md-2">
-                		<div>
-                        <select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
-                            <option value="on">{{:: 'on' | translate}}</option>
-                            <option value="missing">{{:: 'on-missing-info' | translate}}</option>
-                            <option value="off">{{:: 'off' | translate}}</option>
-                        </select>
-                    </div>
-                </div>
-                <kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
-            </div>
-            <div class="form-group">
                 <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
                 <div class="col-md-6">
                     <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -97,6 +84,19 @@
                 </div>
                 <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="firstBrokerLoginFlowAlias"
+                                ng-model="identityProvider.firstBrokerLoginFlowAlias"
+                                ng-options="flow.alias as flow.alias for flow in authFlows"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
 
         <div class="form-group">
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login.ftl b/forms/common-themes/src/main/resources/theme/base/login/login.ftl
index 925f99c..5786807 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login.ftl
@@ -13,7 +13,11 @@
                     </div>
 
                     <div class="${properties.kcInputWrapperClass!}">
-                        <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
+                        <#if usernameEditDisabled??>
+                            <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" disabled />
+                        <#else>
+                            <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
+                        </#if>
                     </div>
                 </div>
 
@@ -29,7 +33,7 @@
 
                 <div class="${properties.kcFormGroupClass!}">
                     <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
-                        <#if realm.rememberMe>
+                        <#if realm.rememberMe && !usernameEditDisabled??>
                             <div class="checkbox">
                                 <label>
                                     <#if login.rememberMe??>
@@ -56,7 +60,7 @@
             </form>
         </#if>
     <#elseif section = "info" >
-        <#if realm.password && realm.registrationAllowed>
+        <#if realm.password && realm.registrationAllowed && !usernameEditDisabled??>
             <div id="kc-registration">
                 <span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
new file mode 100644
index 0000000..02923d9
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
@@ -0,0 +1,21 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=false; section>
+    <#if section = "title">
+        ${msg("confirmLinkIdpTitle")}
+    <#elseif section = "header">
+         ${msg("confirmLinkIdpTitle")}
+    <#elseif section = "form">
+        <div id="kc-error-message">
+            <p class="instruction">${message.summary}</p>
+        </div>
+
+        <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
+
+                <div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
+                    <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
+                    <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
+                </div>
+
+        </form>
+    </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
new file mode 100644
index 0000000..ab3e83e
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
@@ -0,0 +1,15 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+    <#if section = "title">
+        ${msg("emailLinkIdpTitle", idpAlias)}
+    <#elseif section = "header">
+        ${msg("emailLinkIdpTitle", idpAlias)}
+    <#elseif section = "form">
+        <p class="instruction">
+            ${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
+        </p>
+        <p class="instruction">
+            ${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
+        </p>
+    </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
index 584bea3..458884c 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
@@ -6,7 +6,7 @@
         ${msg("loginProfileTitle")}
     <#elseif section = "form">
         <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
-            <#if realm.editUsernameAllowed>
+            <#if user.editUsernameAllowed>
                 <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
                     <div class="${properties.kcLabelWrapperClass!}">
                         <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 3445de5..d968379 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -197,4 +197,5 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 6fa441c..803ef2d 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -79,6 +79,11 @@ emailVerifyInstruction1=An email with instructions to verify your email address 
 emailVerifyInstruction2=Haven''t received a verification code in your email?
 emailVerifyInstruction3=to re-send the email.
 
+emailLinkIdpTitle=Link {0}
+emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you.
+emailLinkIdp2=Haven''t received a verification code in your email?
+emailLinkIdp3=to re-send the email.
+
 backToLogin=&laquo; Back to Login
 
 emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
@@ -132,13 +137,19 @@ invalidTotpMessage=Invalid authenticator code.
 usernameExistsMessage=Username already exists.
 emailExistsMessage=Email already exists.
 
-federatedIdentityEmailExistsMessage=User with email already exists. Please login to account management to link the account.
-federatedIdentityUsernameExistsMessage=User with username already exists. Please login to account management to link the account.
+federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account.
+
+confirmLinkIdpTitle=Account already exists
+federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue?
+federatedIdentityConfirmReauthenticateMessage=Authenticate as {0} to link your account with {1}
+confirmLinkIdpReviewProfile=Review profile info
+confirmLinkIdpContinue=Link {0} with existing account
 
 configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
 updateProfileMessage=You need to update your user profile to activate your account.
 updatePasswordMessage=You need to change your password to activate your account.
 verifyEmailMessage=You need to verify your email address to activate your account.
+linkIdpMessage=You need to verify your email address to link your account with {0}.
 
 emailSentMessage=You should receive an email shortly with further instructions.
 emailSendErrorMessage=Failed to send email, please try again later.
@@ -181,6 +192,7 @@ couldNotObtainTokenMessage=Could not obtain token from identity provider.
 unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider.
 unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider.
 identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider.
+identityProviderDifferentUserMessage=Authenticated as {0}, but expected to be authenticated as {1}
 couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider.
 unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider.
 invalidAccessCodeMessage=Invalid access code.
@@ -188,6 +200,7 @@ sessionNotActiveMessage=Session not active.
 invalidCodeMessage=An error occurred, please login again through your application.
 identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
 identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
+identityProviderLinkSuccess=Your account was successfully linked with {0} account {1} .
 realmSupportsNoCredentialsMessage=Realm does not support any credential type.
 identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
 emailVerifiedMessage=Your email address has been verified.
@@ -196,7 +209,8 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Back to Application
 missingParameterMessage=Missing parameters\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_es.properties
new file mode 100644
index 0000000..ff43eed
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_es.properties
@@ -0,0 +1,205 @@
+doLogIn=Iniciar sesi\u00F3n
+doRegister=Reg\u00EDstrate
+doCancel=Cancelar
+doSubmit=Enviar
+doYes=S\u00ED
+doNo=No
+doContinue=Continuar
+doAccept=Aceptar
+doDecline=Declinar
+doForgotPassword=\u00BFHas olvidado tu contrase\u00F1a?
+doClickHere=Haz clic aqu\u00ED
+doImpersonate=Personificar
+kerberosNotConfigured=Kerberos no configurado
+kerberosNotConfiguredTitle=Kerberos no configurado
+bypassKerberosDetail=O bien no est\u00E1s identificado mediante Kerberos o tu navegador no est\u00E1 configurado para identificarse mediante Kerberos. Por favor haz clic para identificarte por otro medio.
+kerberosNotSetUp=Kerberos no est\u00E1 configurado. No puedes identificarte.
+registerWithTitle=Reg\u00EDstrate con {0}
+registerWithTitleHtml=Reg\u00EDstrate con <strong>{0}</strong>
+loginTitle=Inicia sesi\u00F3n en {0}
+loginTitleHtml=Inicia sesi\u00F3n en {0}
+impersonateTitle={0}\u00A0Personificar Usuario
+impersonateTitleHtml=<strong>{0}</strong> Personificar Usuario</strong>
+realmChoice=Dominio
+unknownUser=Usuario desconocido
+loginTotpTitle=Configura tu aplicaci\u00F3n de identificaci\u00F3n m\u00F3vil
+loginProfileTitle=Actualiza la informaci\u00F3n de tu cuenta
+loginTimeout=Has tardado demasiado en identificarte. Inicia de nuevo la identificaci\u00F3n.
+oauthGrantTitle=Concesi\u00F3n OAuth
+oauthGrantTitleHtml=Acceso temporal para <strong>{0}</strong> solicitado por
+errorTitle=Lo sentimos...
+errorTitleHtml=Lo <strong>sentimos</strong>...
+emailVerifyTitle=Verificaci\u00F3n del email
+emailForgotTitle=\u00BFHas olvidado tu contrase\u00F1a?
+updatePasswordTitle=Modificaci\u00F3n de contrase\u00F1a
+codeSuccessTitle=C\u00F3digo de \u00E9xito
+codeErrorTitle=C\u00F3digo de error: {0}
+
+termsTitle=T\u00E9rminos y Condiciones
+termsTitleHtml=T\u00E9rminos y Condiciones
+termsText=<p>T\u00E9rmines y condiciones a definir</p>
+
+recaptchaFailed=Reconocimiento de texto inv\u00E1lido
+recaptchaNotConfigured=El reconocimiento de texto es obligatorio pero no est\u00E1 configurado
+consentDenied=Consentimiento rechazado.
+
+noAccount=\u00BFUsuario nuevo?
+username=Usuario
+usernameOrEmail=Usuario o email
+firstName=Nombre
+givenName=Nombre de pila
+fullName=Nombre completo
+lastName=Apellidos
+familyName=Apellidos
+email=Email
+password=Contrase\u00F1a
+passwordConfirm=Confirma la contrase\u00F1a
+passwordNew=Nueva contrase\u00F1a
+passwordNewConfirm=Confirma la nueva contrase\u00F1a
+rememberMe=Seguir conectado
+authenticatorCode=C\u00F3digo de identificaci\u00F3n
+address=Direcci\u00F3n
+street=Calle
+locality=Ciudad o Municipio
+region=Estado, Provincia, o Regi\u00F3n
+postal_code=C\u00F3digo Postal
+country=Pa\u00EDs
+emailVerified=Email verificado
+gssDelegationCredential=GSS Delegation Credential
+
+loginTotpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
+loginTotpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
+loginTotpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
+loginTotpOneTime=C\u00F3digo de un solo uso
+
+oauthGrantRequest=\u00BFQuieres permitir estos privilegios de acceso?
+inResource=en
+
+emailVerifyInstruction1=Te hemos enviado un email con instrucciones para verificar tu email.
+emailVerifyInstruction2=\u00BFNo has recibido un c\u00F3digo de verificaci\u00F3n en tu email?
+emailVerifyInstruction3=para reenviar el email.
+
+backToLogin=&laquo; Volver a la identificaci\u00F3n
+
+emailInstruction=Indica tu usuario o email y te enviaremos instruciones indicando como generar una nueva contrase\u00F1a.
+
+copyCodeInstruction=Por favor, copia y pega este c\u00F3digo en tu aplicaci\u00F3n:
+
+personalInfo=Informaci\u00F3n personal:
+role_admin=Admin
+role_realm-admin=Administrador del dominio
+role_create-realm=Crear dominio
+role_create-client=Crear cliente
+role_view-realm=Ver dominio
+role_view-users=Ver usuarios
+role_view-applications=Ver aplicaciones
+role_view-clients=Ver clientes
+role_view-events=Ver eventos
+role_view-identity-providers=Ver proveedores de identidad
+role_manage-realm=Gestionar dominio
+role_manage-users=Gestionar usuarios
+role_manage-applications=Gestionar aplicaciones
+role_manage-identity-providers=Gestionar proveedores de identidad
+role_manage-clients=Gestionar clientes
+role_manage-events=Gestionar eventos
+role_view-profile=Ver perfil
+role_manage-account=Gestionar cuenta
+role_read-token=Leer token
+role_offline-access=Acceso sin conexi\u00F3n
+client_account=Cuenta
+client_security-admin-console=Consola de Administraci\u00F3n de Seguridad
+client_realm-management=Gesti\u00F3n del dominio
+client_broker=Broker
+
+invalidUserMessage=Usuario o contrase\u00F1a incorrectos.
+invalidEmailMessage=Email no v\u00E1lido
+accountDisabledMessage=La cuenta est\u00E1 desactivada, contacta con el administrador.
+accountTemporarilyDisabledMessage=La cuenta est\u00E1 temporalmente desactivada, contacta con el administrador o int\u00E9ntalo de nuevo m\u00E1s tarde.
+expiredCodeMessage=Se agot\u00F3 el tiempo m\u00E1ximo para la identificaci\u00F3n. Por favor identificate de nuevo.
+
+missingFirstNameMessage=Por favor indica tu nombre.
+missingLastNameMessage=Por favor indica tus apellidos.
+missingEmailMessage=Por favor indica tu email.
+missingUsernameMessage=Por favor indica tu usuario.
+missingPasswordMessage=Por favor indica tu contrase\u00F1a.
+missingTotpMessage=Por favor indica tu c\u00F3digo de autenticaci\u00F3n
+notMatchPasswordMessage=Las contrase\u00F1as no coinciden.
+
+invalidPasswordExistingMessage=La contrase\u00F1a actual no es correcta.
+invalidPasswordConfirmMessage=La confirmaci\u00F3n de contrase\u00F1a no coincide.
+invalidTotpMessage=El c\u00F3digo de autenticaci\u00F3n no es v\u00E1lido.
+
+usernameExistsMessage=El nombre de usuario ya existe
+emailExistsMessage=El email ya existe
+
+federatedIdentityEmailExistsMessage=Ya existe un usuario con este email. Por favor accede a la gesti\u00F3n de tu cuenta para enlazarlo.
+federatedIdentityUsernameExistsMessage=Ya existe un usuario con este nombre de usuario. Por favor accede a la gesti\u00F3n de tu cuenta para enlazarlo.
+
+configureTotpMessage=Tienes que configurar la aplicaci\u00F3n m\u00F3vil de identificaci\u00F3n para activar tu cuenta.
+updateProfileMessage=Tienes que actualizar tu perfil de usuario para activar tu cuenta.
+updatePasswordMessage=Tienes que cambiar tu contrase\u00F1a para activar tu cuenta.
+verifyEmailMessage=Tienes que verificar tu email para activar tu cuenta.
+
+emailSentMessage=En breve deber\u00EDas recibir un mensaje con m\u00E1s instrucciones
+emailSendErrorMessage=Fall\u00F3 el env\u00EDo del email, por favor int\u00E9ntalo de nuevo m\u00E1s tarde.
+
+accountUpdatedMessage=Tu cuenta se ha actualizado.
+accountPasswordUpdatedMessage=Tu contrase\u00F1a se ha actualizado.
+
+noAccessMessage=Sin acceso
+
+invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
+invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
+invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
+invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
+invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular.
+invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as.
+
+failedToProcessResponseMessage=Fallo al procesar la respuesta
+httpsRequiredMessage=HTTPS obligatorio
+realmNotEnabledMessage=El dominio no est\u00E1 activado
+invalidRequestMessage=Petici\u00F3n incorrecta
+failedLogout=Fall\u00F3 la desconexi\u00F3n.
+unknownLoginRequesterMessage=Solicitante de identificaci\u00F3n desconocido
+loginRequesterNotEnabledMessage=El solicitante de inicio de sesi\u00F3n est\u00E1 desactivado
+bearerOnlyMessage=Las aplicaciones Bearer-only no pueden iniciar sesi\u00F3n desde el navegador.
+directGrantsOnlyMessage=Los clientes de tipo Direct-grants-only no pueden iniciar sesi\u00F3n desde el navegador.
+invalidRedirectUriMessage=La URI de redirecci\u00F3n no es correcta
+unsupportedNameIdFormatMessage=NameIDFormat no soportado
+invlidRequesterMessage=Solicitante no v\u00E1lido
+registrationNotAllowedMessage=El registro no est\u00E1 permitido
+resetCredentialNotAllowedMessage=El renicio de las credenciales no est\u00E1 permitido
+
+permissionNotApprovedMessage=Permiso no aprobado.
+noRelayStateInResponseMessage=Sin estado de retransmisi\u00F3n en la respuesta del proveedor de identidad.
+identityProviderAlreadyLinkedMessage=La identidad devuelta por el proveedor de identidad ya est\u00E1 asociada a otro usuario.
+insufficientPermissionMessage=Permisos insuficientes para enlazar identidades.
+couldNotProceedWithAuthenticationRequestMessage=No se pudo continuar con la petici\u00F3n de autenticaci\u00F3n al proveedor de identidad.
+couldNotObtainTokenMessage=.No se pudo obtener el c\u00F3digo del proveedor de identidad
+unexpectedErrorRetrievingTokenMessage=Error inesperado obteniendo el token del proveedor de identidad
+unexpectedErrorHandlingResponseMessage=Error inesperado procesando la respuesta del proveedor de identidad.
+identityProviderAuthenticationFailedMessage=Fall\u00F3 la autenticaci\u00F3n. No fue posible autenticarse en el proveedor de identidad.
+couldNotSendAuthenticationRequestMessage=No se pudo enviar la petici\u00F3n de identificaci\u00F3n al proveedor de identidad.
+unexpectedErrorHandlingRequestMessage=Error inesperado durante la petici\u00F3n de identificaci\u00F3n al proveedor de identidad.
+invalidAccessCodeMessage=C\u00F3digo de acceso no v\u00E1lido.
+sessionNotActiveMessage=La sesi\u00F3n no est\u00E1 activa
+invalidCodeMessage=Ha ocurrido un error, por favor identificate de nuevo desde tu aplicaci\u00F3n.
+identityProviderUnexpectedErrorMessage=Error no esperado intentado autenticar en el proveedor de identidad.
+identityProviderNotFoundMessage=No se encontr\u00F3 un proveedor de identidad.
+realmSupportsNoCredentialsMessage=El dominio no soporta ning\u00FAn tipo de credenciales.
+identityProviderNotUniqueMessage=El dominio soporta m\u00FAltiples proveedores de identidad. No se pudo determinar el proveedor de identidad que deber\u00EDa ser utilizado para identificarse.
+emailVerifiedMessage=Tu email ha sido verificado.
+
+locale_de=German
+locale_en=English
+locale_it=Italian
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_fr=Fran\u00C3\u00A7ais
+locale_es=Espa\u00F1ol
+
+backToApplication=&laquo; Volver a la aplicaci\u00F3n
+missingParameterMessage=Par\u00E1metros que faltan: {0}
+clientNotFoundMessage=Cliente no encontrado
+invalidParameterMessage=Par\u00E1metro no v\u00E1lido: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties
index 22bd4ae..495dde6 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties
@@ -6,48 +6,48 @@ doYes=Oui
 doNo=Non
 doContinue=Continuer
 doAccept=Accepter
-doDecline=Decliner
-doForgotPassword=Mot de passe oublié ?
+doDecline=D\u00e9cliner
+doForgotPassword=Mot de passe oubli\u00e9 ?
 doClickHere=Cliquez ici
 doImpersonate=Impersonate
-kerberosNotConfigured=Kerberos non configuré
-kerberosNotConfiguredTitle=Kerberos non configuré
-bypassKerberosDetail=Si vous n''êtes pas connecté via Kerberos ou bie que votre navigateur n''est opas configurer pour la connexion via Kerberos.  Veuillez cliquer pour vous connecter via un autre moyen.
-kerberosNotSetUp=Kerberos n''est pas configuré. Connexion impossible.
+kerberosNotConfigured=Kerberos non configur\u00e9
+kerberosNotConfiguredTitle=Kerberos non configur\u00e9
+bypassKerberosDetail=Si vous n''\u00eates pas connect\u00e9 via Kerberos ou bien que votre navigateur n''est pas configur\u00e9 pour la connexion via Kerberos.  Veuillez cliquer pour vous connecter via un autre moyen.
+kerberosNotSetUp=Kerberos n''est pas configur\u00e9. Connexion impossible.
 registerWithTitle=Enregistrement avec {0}
-registerWithTitleHtml=Enregistrement avec<strong>{0}</strong>
-loginTitle=Connecté {0}
-loginTitleHtml=Connecté <strong>{0}</strong>
+registerWithTitleHtml=Enregistrement avec <strong>{0}</strong>
+loginTitle=Se connecter \u00e0 {0}
+loginTitleHtml=Se connecter \u00e0 <strong>{0}</strong>
 impersonateTitle={0} utilisateur impersonate
 impersonateTitleHtml=<strong>{0}</strong> utilisateur impersonate</strong>
 realmChoice=Domaine
 unknownUser=Utilisateur inconnu
 loginTotpTitle=Configuration de l''authentification par mobile
-loginProfileTitle=Mise à jour du compte
-loginTimeout=Le temps imparti pour la connexion est écoulé. Le processus de connexion redémarre depuis le debut.
+loginProfileTitle=Mise \u00e0 jour du compte
+loginTimeout=Le temps imparti pour la connexion est \u00e9coul\u00e9. Le processus de connexion red\u00e9marre depuis le d\u00e9but.
 oauthGrantTitle=OAuth Grant
-oauthGrantTitleHtml=Accès temporaire pour <strong>{0}</strong> demandé par
-errorTitle=Nous sommes désolé ...
-errorTitleHtml=Nous sommes <strong>désolé</strong> ...
-emailVerifyTitle=Vérification du courriel
-emailForgotTitle=Mot de passe oublié ?
-updatePasswordTitle=Mise à jour du mot de passe
-codeSuccessTitle=Code succès
-codeErrorTitle=Code Erreur\: {0}
+oauthGrantTitleHtml=Acc\u00e8s temporaire pour <strong>{0}</strong> demand\u00e9 par
+errorTitle=Nous sommes d\u00e9sol\u00e9 ...
+errorTitleHtml=Nous sommes <strong>d\u00e9sol\u00e9</strong> ...
+emailVerifyTitle=V\u00e9rification du courriel
+emailForgotTitle=Mot de passe oubli\u00e9 ?
+updatePasswordTitle=Mise \u00e0 jour du mot de passe
+codeSuccessTitle=Code succ\u00e8s
+codeErrorTitle=Code d''erreur\: {0}
 
 termsTitle=Termes et Conditions
 termsTitleHtml=Termes et Conditions
-termsText=<p>Termes et conditions à définir</p>
+termsText=<p>Termes et conditions \u00e0 d\u00e9finir</p>
 
 recaptchaFailed=Re-captcha invalide
-recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configuré
-consentDenied=Consentement refusé.
+recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configur\u00e9
+consentDenied=Consentement refus\u00e9.
 
 noAccount=Nouvel utilisateur?
 username=Nom d''utilisateur
 usernameOrEmail=Nom d''utilisateur ou courriel
-firstName=Prénom
-givenName=Prénom
+firstName=Pr\u00e9nom
+givenName=Pr\u00e9nom
 fullName=Nom complet
 lastName=Nom
 familyName=Nom de famille
@@ -55,38 +55,38 @@ email=Courriel
 password=Mot de passe
 passwordConfirm=Confirmation du mot de passe
 passwordNew=Nouveau mot de passe
-passwordNewConfirm=Confirmation du nouveau not de passe
+passwordNewConfirm=Confirmation du nouveau mot de passe
 rememberMe=Se souvenir de moi
-authenticatorCode=Code à usage unique
+authenticatorCode=Code \u00e0 usage unique
 address=Adresse
 street=Rue
-locality=Ville ou Localité
-region=État, Province, ou Région
+locality=Ville ou Localit\u00e9
+region=\u00c9tat, Province ou R\u00e9gion
 postal_code=Code postal
 country=Pays
-emailVerified=Courriel vérifié
-gssDelegationCredential=GSS Delegation Credential
+emailVerified=Courriel v\u00e9rifi\u00e9
+gssDelegationCredential=Accr\u00e9ditation de d\u00e9l\u00e9gation GSS
 
 loginTotpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
-loginTotpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
+loginTotpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9.
 loginTotpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
-loginTotpOneTime=Code à usage unique
+loginTotpOneTime=Code \u00e0 usage unique
 
-oauthGrantRequest=Voulez-vous accorder ces privileges d''accès ?
+oauthGrantRequest=Voulez-vous accorder ces privil\u00e8ges d''acc\u00e8s ?
 inResource=dans
 
-emailVerifyInstruction1=Un courriel avec des instructions à suivre vous a été envoyé.
-emailVerifyInstruction2=Vous n''avez pas reçu de code dans le courriel ?
+emailVerifyInstruction1=Un courriel avec des instructions \u00e0 suivre vous a \u00e9t\u00e9 envoy\u00e9.
+emailVerifyInstruction2=Vous n''avez pas re\u00e7u de code dans le courriel?
 emailVerifyInstruction3=Pour renvoyer le courriel.
 
-backToLogin=&laquo; Retour à la connexion
+backToLogin=&laquo; Retour \u00e0 la connexion
 
-emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un email va vous \u00eatre envoyer vous permettant de créer un nouveau mot de passe.
+emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un courriel va vous \u00eatre envoy\u00e9 vous permettant de cr\u00e9er un nouveau mot de passe.
 
-copyCodeInstruction=Copiez le code et recopiez le dans votre application :
+copyCodeInstruction=Copiez le code et recopiez le dans votre application:
 
 personalInfo=Information personnelle:
-role_admin=Adminitrateur
+role_admin=Administrateur
 role_realm-admin=Administrateur du domaine
 role_create-realm=Cr\u00e9er un domaine
 role_view-realm=Voir un domaine
@@ -99,8 +99,8 @@ role_manage-realm=G\u00e9rer le domaine
 role_manage-users=G\u00e9rer les utilisateurs
 role_manage-applications=G\u00e9rer les applications
 role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s
-role_manage-clients=G\u00e9rer les  clients
-role_manage-events=G\u00e9rer les  \u00e9v\u00e9nements
+role_manage-clients=G\u00e9rer les clients
+role_manage-events=G\u00e9rer les \u00e9v\u00e9nements
 role_view-profile=Voir le profile
 role_manage-account=G\u00e9rer le compte
 role_read-token=Lire le jeton d''authentification
@@ -111,9 +111,9 @@ client_realm-management=Gestion du domaine
 client_broker=Broker
 
 invalidUserMessage=Nom d''utilisateur ou mot de passe invalide.
-invalidEmailMessage=Adresse courriel invalide.
-accountDisabledMessage=Compte désactivé, contactez votre administrateur.
-accountTemporarilyDisabledMessage=Ce compte est temporairement désactivé, contactez votre administrateur ou bien réassayer plus tard.
+invalidEmailMessage=Courriel invalide.
+accountDisabledMessage=Compte d\u00e9sactiv\u00e9, contactez votre administrateur.
+accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, contactez votre administrateur ou bien r\u00e9assayer plus tard.
 expiredCodeMessage=Fin de connexion. Veuillez vous reconnecter.
 
 missingFirstNameMessage=Veuillez entrer votre pr\u00e9nom.
@@ -131,75 +131,76 @@ invalidTotpMessage=Le code d''authentification est invalide.
 usernameExistsMessage=Le nom d''utilisateur existe d\u00e9j\u00e0.
 emailExistsMessage=Le courriel existe d\u00e9j\u00e0.
 
-federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
-federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
+federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe d\u00e9j\u00e0. Veuillez vous connect\u00e9 au gestionnaire de compte pour lier le compte.
+federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe d\u00e9j\u00e0. Veuillez vous connect\u00e9 au gestionnaire de compte pour lier le compte.
 
 configureTotpMessage=Vous devez configurer l''authentification par mobile pour activer votre compte.
-updateProfileMessage=Vous devez mettre à jour votre profile pour activer votre compte.
+updateProfileMessage=Vous devez mettre \u00e0 jour votre profile pour activer votre compte.
 updatePasswordMessage=Vous devez changer votre mot de passe pour activer votre compte.
-verifyEmailMessage=Vous devez vérifier votre courriel pour activer votre compte.
+verifyEmailMessage=Vous devez v\u00e9rifier votre courriel pour activer votre compte.
 
 emailSentMessage=Vous devriez recevoir rapidement un courriel avec de plus ample instructions.
 emailSendErrorMessage=Erreur lors de l''envoie du courriel, veuillez essayer plus tard.
 
-accountUpdatedMessage=Votre compte a été mis à jour.
-accountPasswordUpdatedMessage=Votre mot de passe a été mis à jour.
+accountUpdatedMessage=Votre compte a \u00e9t\u00e9 mis \u00e0 jour.
+accountPasswordUpdatedMessage=Votre mot de passe a \u00e9t\u00e9 mis \u00e0 jour.
 
-noAccessMessage=Aucun accès
+noAccessMessage=Aucun acc\u00e8s
 
-invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}.
-invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s).
-invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.
-invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.
-invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
-invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur.
-invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle.
-invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
+invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
+invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
+invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
+invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
+invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
+invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas \u00eatre identique au nom d''utilisateur.
+invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l''expression rationnelle.
+invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
 
 
-failedToProcessResponseMessage=Erreur lors du traitement de la réponse
+failedToProcessResponseMessage=Erreur lors du traitement de la r\u00e9ponse
 httpsRequiredMessage=Le protocole HTTPS est requis
-realmNotEnabledMessage=Le domaine n''est pas activé
-invalidRequestMessage=Requete invalide
-failedLogout=La déconnexion a échouée
+realmNotEnabledMessage=Le domaine n''est pas activ\u00e9
+invalidRequestMessage=Requ\u00eate invalide
+failedLogout=La d\u00e9connexion a \u00e9chou\u00e9e
 unknownLoginRequesterMessage=Compte inconnu du demandeur
 loginRequesterNotEnabledMessage=La connexion du demandeur n''est pas active
-bearerOnlyMessage=Les applications Bearer-only ne sont pas autorisées à initier la connexion par navigateur.
-directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autorisés à initier la connexion par navigateur.
+bearerOnlyMessage=Les applications Bearer-only ne sont pas autoris\u00e9es \u00e0 initier la connexion par navigateur.
+directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autoris\u00e9s \u00e0 initier la connexion par navigateur.
 invalidRedirectUriMessage=L''uri de redirection est invalide
-unsupportedNameIdFormatMessage=NameIDFormat non supporté
+unsupportedNameIdFormatMessage=NameIDFormat non support\u00e9
 invlidRequesterMessage=Demandeur invalide
-registrationNotAllowedMessage=L''enregistrement n''est pas autorisé
-resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autorisé
-
-permissionNotApprovedMessage=La permission n''est pas approuvée.
-noRelayStateInResponseMessage=Aucun état de relais dans la réponse du fournisseur d''identité.
-identityProviderAlreadyLinkedMessage=L''identité retournée par le fournisseur d''identité est déjà liée avec un autre utilisateur.
-insufficientPermissionMessage=Permissions insuffisantes pour lier les identités.
-couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requête d''authentification vers le fournisseur d''identité.
-couldNotObtainTokenMessage=Impossible de récuperer le jeton du fournisseur d''identité.
-unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la récupération du jeton provenant du fournisseur d''identité.
-unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la réponse provenant du fournisseur d''identité.
-identityProviderAuthenticationFailedMessage=L''authentification a échouée. Impossible de s''authentifier avec le fournisseur d''identité.
-couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requête d''authentification vers le fournisseur d''identité.
-unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requête vers le fournisseur d''identité.
-invalidAccessCodeMessage=Code d''accès invalide.
+registrationNotAllowedMessage=L''enregistrement n''est pas autoris\u00e9
+resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autoris\u00e9
+
+permissionNotApprovedMessage=La permission n''est pas approuv\u00e9e.
+noRelayStateInResponseMessage=Aucun \u00e9tat de relais dans la r\u00e9ponse du fournisseur d''identit\u00e9.
+identityProviderAlreadyLinkedMessage=L''identit\u00e9 retourn\u00e9e par le fournisseur d''identit\u00e9 est d\u00e9j\u00e0 li\u00e9e avec un autre utilisateur.
+insufficientPermissionMessage=Permissions insuffisantes pour lier les identit\u00e9s.
+couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requ\u00eate d''authentification vers le fournisseur d''identit\u00e9.
+couldNotObtainTokenMessage=Impossible de r\u00e9cup\u00e9rer le jeton du fournisseur d''identit\u00e9.
+unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la r\u00e9cup\u00e9ration du jeton provenant du fournisseur d''identit\u00e9.
+unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la r\u00e9ponse provenant du fournisseur d''identit\u00e9.
+identityProviderAuthenticationFailedMessage=L''authentification a \u00e9chou\u00e9e. Impossible de s''authentifier avec le fournisseur d''identit\u00e9.
+couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requ\u00eate d''authentification vers le fournisseur d''identit\u00e9.
+unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requ\u00eate vers le fournisseur d''identit\u00e9.
+invalidAccessCodeMessage=Code d''acc\u00e8s invalide.
 sessionNotActiveMessage=La session n''est pas active.
-invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter à votre application.
-identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identité.
-identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identité avec ce identifiant.
-realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accréditation.
-identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identité. Impossible de déterminer fournisseurs d''identité avec lequel s''authentifier.
-emailVerifiedMessage=Votre adresse courriel a été vérifiée.
+invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter \u00e0 votre application.
+identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identit\u00e9.
+identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identit\u00e9 avec cet identifiant.
+realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accr\u00e9ditation.
+identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identit\u00e9. Impossible de d\u00e9terminer le fournisseur d''identit\u00e9 avec lequel s''authentifier.
+emailVerifiedMessage=Votre courriel a \u00e9t\u00e9 v\u00e9rifi\u00e9.
 
 locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 
-backToApplication=&laquo; Revenir à l''application
-missingParameterMessage=Paramètres manquants \: {0}
+backToApplication=&laquo; Revenir \u00e0 l''application
+missingParameterMessage=Param\u00e8tres manquants\: {0}
 clientNotFoundMessage=Client inconnu.
-invalidParameterMessage=Paramètres invalide \: {0}
+invalidParameterMessage=Param\u00e8tre invalide\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
index e353153..026c7a4 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -189,7 +189,8 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Torna all''Applicazione
 missingParameterMessage=Parametri Mancanti\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index e2a2257..7c44eeb 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -194,7 +194,8 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (BR)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Voltar para o aplicativo
 missingParameterMessage=Par\u00E2metros que faltam\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/html/identity-provider-link.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/html/identity-provider-link.ftl
new file mode 100644
index 0000000..9c2db80
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/html/identity-provider-link.ftl
@@ -0,0 +1,5 @@
+<html>
+<body>
+${msg("identityProviderLinkBodyHtml", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration)}
+</body>
+</html>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties
index fd9d909..f4a945c 100755
--- a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties
@@ -1,6 +1,9 @@
 emailVerificationSubject=Verify email
 emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn''t create this account, just ignore this message.
 emailVerificationBodyHtml=<p>Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you didn''t create this account, just ignore this message.</p>
+identityProviderLinkSubject=Link {0}
+identityProviderLinkBody=Someone wants to link your "{1}" account with "{0}" account of user {2} . If this was you, click the link below to link accounts\n\n{3}\n\nThis link will expire within {4} minutes.\n\nIf you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.
+identityProviderLinkBodyHtml=<p>Someone wants to link your <b>{1}</b> account with <b>{0}</b> account of user {2} . If this was you, click the link below to link accounts</p><p><a href="{3}">{3}</a></p><p>This link will expire within {4} minutes.</p><p>If you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.</p>
 passwordResetSubject=Reset password
 passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
 passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_es.properties
new file mode 100644
index 0000000..1616014
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_es.properties
@@ -0,0 +1,21 @@
+emailVerificationSubject=Verificaci\u00F3n de email
+emailVerificationBody=Alguien ha creado una cuenta de {2} con esta direcci\u00F3n de email. Si has sido t\u00FA, haz click en el enlace siguiente para verificar tu direcci\u00F3n de email.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutos.\n\nSi t\u00FA no has creado esta cuenta, simplemente ignora este mensaje.
+emailVerificationBodyHtml=<p>Alguien ha creado una cuenta de {2} con esta direcci\u00F3n de email. Si has sido t\u00FA, haz click en el enlace siguiente para verificar tu direcci\u00F3n de email.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutos.</p><p>Si t\u00FA no has creado esta cuenta, simplemente ignora este mensaje.</p>
+passwordResetSubject=Reiniciar contrase\u00F1a
+passwordResetBody=Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido t\u00FA, haz clic en el enlace siguiente para reiniciarlas.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutos.\n\nSi no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.
+passwordResetBodyHtml=<p>Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido t\u00FA, haz clic en el enlace siguiente para reiniciarlas.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutos.</p><p>Si no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.</p>
+executeActionsSubject=Actualiza tu cuenta
+executeActionsBody=El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutes.\n\nSi no est\u00E1s al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.
+executeActionsBodyHtml=<p>El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutes.</p><p>Si no est\u00E1s al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.</p>
+eventLoginErrorSubject=Fallo en el inicio de sesi\u00F3n
+eventLoginErrorBody=Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventLoginErrorBodyHtml=<p>Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
+eventRemoveTotpSubject=Borrado TOTP
+eventRemoveTotpBody=TOTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventRemoveTotpBodyHtml=<p>TOTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
+eventUpdatePasswordSubject=Actualizaci\u00F3n de contrase\u00F1a
+eventUpdatePasswordBody=Tu contrase\u00F1a se ha actualizado el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventUpdatePasswordBodyHtml=<p>Tu contrase\u00F1a se ha actualizado el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
+eventUpdateTotpSubject=Actualizaci\u00F3n de TOTP
+eventUpdateTotpBody=TOTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventUpdateTotpBodyHtml=<p>TOTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/text/identity-provider-link.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/text/identity-provider-link.ftl
new file mode 100644
index 0000000..a8c0d54
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/text/identity-provider-link.ftl
@@ -0,0 +1 @@
+${msg("identityProviderLinkBody", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration)}
\ No newline at end of file
diff --git a/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java b/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java
index 2304ce5..151eec7 100755
--- a/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java
+++ b/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java
@@ -10,10 +10,14 @@ import org.keycloak.provider.Provider;
  */
 public interface EmailProvider extends Provider {
 
+    String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
+
     public EmailProvider setRealm(RealmModel realm);
 
     public EmailProvider setUser(UserModel user);
 
+    public EmailProvider setAttribute(String name, Object value);
+
     public void sendEvent(Event event) throws EmailException;
 
     /**
@@ -26,6 +30,11 @@ public interface EmailProvider extends Provider {
     public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException;
 
     /**
+     * Send to confirm that user wants to link his account with identity broker link
+     */
+    void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException;
+
+    /**
      * Change password email requested by admin
      *
      * @param link
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
index 9f55e37..3a04fbf 100755
--- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
@@ -1,8 +1,11 @@
 package org.keycloak.email.freemarker;
 
 import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
@@ -17,6 +20,7 @@ import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
 import org.jboss.logging.Logger;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.email.freemarker.beans.EventBean;
@@ -28,6 +32,7 @@ import org.keycloak.freemarker.FreeMarkerUtil;
 import org.keycloak.freemarker.Theme;
 import org.keycloak.freemarker.ThemeProvider;
 import org.keycloak.freemarker.beans.MessageFormatterMethod;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -43,6 +48,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     private FreeMarkerUtil freeMarker;
     private RealmModel realm;
     private UserModel user;
+    private final Map<String, Object> attributes = new HashMap<String, Object>();
 
     public FreeMarkerEmailProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
@@ -62,6 +68,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     }
 
     @Override
+    public EmailProvider setAttribute(String name, Object value) {
+        attributes.put(name, value);
+        return this;
+    }
+
+    @Override
     public void sendEvent(Event event) throws EmailException {
         Map<String, Object> attributes = new HashMap<String, Object>();
         attributes.put("user", new ProfileBean(user));
@@ -84,6 +96,27 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     }
 
     @Override
+    public void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException {
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("user", new ProfileBean(user));
+        attributes.put("link", link);
+        attributes.put("linkExpiration", expirationInMinutes);
+
+        String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+        attributes.put("realmName", realmName);
+
+        BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
+        String idpAlias = brokerContext.getIdpConfig().getAlias();
+        idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+
+        attributes.put("identityProviderContext", brokerContext);
+        attributes.put("identityProviderAlias", idpAlias);
+
+        List<Object> subjectAttrs = Arrays.<Object>asList(idpAlias);
+        send("identityProviderLinkSubject", subjectAttrs, "identity-provider-link.ftl", attributes);
+    }
+
+    @Override
     public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException {
         Map<String, Object> attributes = new HashMap<String, Object>();
         attributes.put("user", new ProfileBean(user));
@@ -111,6 +144,10 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     }
 
     private void send(String subjectKey, String template, Map<String, Object> attributes) throws EmailException {
+        send(subjectKey, Collections.emptyList(), template, attributes);
+    }
+
+    private void send(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
         try {
             ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
             Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
@@ -118,7 +155,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
             attributes.put("locale", locale);
             Properties rb = theme.getMessages(locale);
             attributes.put("msg", new MessageFormatterMethod(locale, rb));
-            String subject = new MessageFormat(rb.getProperty(subjectKey,subjectKey),locale).format(new Object[0]);
+            String subject = new MessageFormat(rb.getProperty(subjectKey,subjectKey),locale).format(subjectAttributes.toArray());
             String textTemplate = String.format("text/%s", template);
             String textBody;
             try {
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
index e34b0ef..9a44db4 100644
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
@@ -5,6 +5,8 @@ package org.keycloak.login;
  */
 public enum LoginFormsPages {
 
-    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
+    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL,
+    LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
+    OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
 
 }
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index fccfce1..eaeb4b4 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -23,6 +23,13 @@ import org.keycloak.provider.Provider;
  */
 public interface LoginFormsProvider extends Provider {
 
+    String UPDATE_PROFILE_CONTEXT_ATTR = "updateProfileCtx";
+
+    String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
+
+    String USERNAME_EDIT_DISABLED = "usernameEditDisabled";
+
+
     /**
      * Adds a script to the html header
      *
@@ -44,6 +51,12 @@ public interface LoginFormsProvider extends Provider {
 
     public Response createInfoPage();
 
+    public Response createUpdateProfilePage();
+
+    public Response createIdpLinkConfirmLinkPage();
+
+    public Response createIdpLinkEmailPage();
+
     public Response createErrorPage();
 
     public Response createOAuthGrant(ClientSessionModel clientSessionModel);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 6125d0b..ef8cc74 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -19,6 +19,9 @@ package org.keycloak.login.freemarker;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
@@ -48,6 +51,7 @@ import org.keycloak.login.freemarker.model.UrlBean;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -129,6 +133,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 page = LoginFormsPages.LOGIN_CONFIG_TOTP;
                 break;
             case UPDATE_PROFILE:
+                UpdateProfileContext userBasedContext = new UserUpdateProfileContext(realm, user);
+                this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, userBasedContext);
+
                 actionMessage = Messages.UPDATE_PROFILE;
                 page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
                 break;
@@ -140,7 +147,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 try {
                     UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
                     builder.queryParam(OAuth2Constants.CODE, accessCode);
-                    builder.queryParam("key", clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
+                    builder.queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
 
                     String link = builder.build(realm.getName()).toString();
                     long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@@ -222,6 +229,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 }
             }
             attributes.put("message", wholeMessage);
+        } else {
+            attributes.put("message", null);
         }
         attributes.put("messagesPerField", messagesPerField);
 
@@ -237,7 +246,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         if (realm != null) {
             attributes.put("realm", new RealmBean(realm));
-            attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
+
+            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+            identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
+            attributes.put("social", new IdentityProviderBean(realm, identityProviders, baseUri, uriInfo));
+
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
 
             if (realm.isInternationalizationEnabled()) {
@@ -268,7 +281,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 attributes.put("totp", new TotpBean(realm, user, baseUri));
                 break;
             case LOGIN_UPDATE_PROFILE:
-                attributes.put("user", new ProfileBean(user, formData));
+                UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
+                attributes.put("user", new ProfileBean(userCtx, formData));
+                break;
+            case LOGIN_IDP_LINK_CONFIRM:
+            case LOGIN_IDP_LINK_EMAIL:
+                BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
+                String idpAlias = brokerContext.getIdpConfig().getAlias();
+                idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+
+                attributes.put("brokerContext", brokerContext);
+                attributes.put("idpAlias", idpAlias);
                 break;
             case REGISTER:
                 attributes.put("register", new RegisterBean(formData));
@@ -371,7 +394,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         if (realm != null) {
             attributes.put("realm", new RealmBean(realm));
-            attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
+
+            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+            identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
+            attributes.put("social", new IdentityProviderBean(realm, identityProviders, baseUri, uriInfo));
+
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
             attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
 
@@ -424,6 +451,32 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public Response createUpdateProfilePage() {
+        // Don't display initial message if we already have some errors
+        if (messageType != MessageType.ERROR) {
+            setMessage(MessageType.WARNING, Messages.UPDATE_PROFILE);
+        }
+
+        return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE);
+    }
+
+
+    @Override
+    public Response createIdpLinkConfirmLinkPage() {
+        return createResponse(LoginFormsPages.LOGIN_IDP_LINK_CONFIRM);
+    }
+
+    @Override
+    public Response createIdpLinkEmailPage() {
+        BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
+        String idpAlias = brokerContext.getIdpConfig().getAlias();
+        idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+        setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
+
+        return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);
+    }
+
+    @Override
     public Response createErrorPage() {
         if (status == null) {
             status = Response.Status.INTERNAL_SERVER_ERROR;
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/LoginFormsUtil.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/LoginFormsUtil.java
new file mode 100644
index 0000000..bbcf1c0
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/LoginFormsUtil.java
@@ -0,0 +1,58 @@
+package org.keycloak.login.freemarker;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * Various util methods, so the logic is not hardcoded in freemarker beans
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LoginFormsUtil {
+
+    // Display just those identityProviders on login screen, which are already linked to "known" established user
+    public static List<IdentityProviderModel> filterIdentityProviders(List<IdentityProviderModel> providers, KeycloakSession session, RealmModel realm,
+                                                                      Map<String, Object> attributes, MultivaluedMap<String, String> formData) {
+
+        Boolean usernameEditDisabled = (Boolean) attributes.get(LoginFormsProvider.USERNAME_EDIT_DISABLED);
+        if (usernameEditDisabled != null && usernameEditDisabled) {
+            String username = formData.getFirst(UserModel.USERNAME);
+            if (username == null) {
+                throw new IllegalStateException("USERNAME_EDIT_DISABLED but username not known");
+            }
+
+            UserModel user = session.users().getUserByUsername(username, realm);
+            if (user == null || !user.isEnabled()) {
+                throw new IllegalStateException("User " + username + " not found or disabled");
+            }
+
+            Set<FederatedIdentityModel> fedLinks = session.users().getFederatedIdentities(user, realm);
+            Set<String> federatedIdentities = new HashSet<>();
+            for (FederatedIdentityModel fedLink : fedLinks) {
+                federatedIdentities.add(fedLink.getIdentityProvider());
+            }
+
+            List<IdentityProviderModel> result = new LinkedList<>();
+            for (IdentityProviderModel idp : providers) {
+                if (federatedIdentities.contains(idp.getAlias())) {
+                    result.add(idp);
+                }
+            }
+            return result;
+        } else {
+            return providers;
+        }
+    }
+}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
index 23c6e69..4d4f1e3 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
@@ -44,9 +44,8 @@ public class IdentityProviderBean {
     private List<IdentityProvider> providers;
     private RealmModel realm;
 
-    public IdentityProviderBean(RealmModel realm, URI baseURI, UriInfo uriInfo) {
+    public IdentityProviderBean(RealmModel realm, List<IdentityProviderModel> identityProviders, URI baseURI, UriInfo uriInfo) {
         this.realm = realm;
-        List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
 
         if (!identityProviders.isEmpty()) {
             Set<IdentityProvider> orderedSet = new TreeSet<>(IdentityProviderComparator.INSTANCE);
@@ -57,7 +56,7 @@ public class IdentityProviderBean {
             }
 
             if (!orderedSet.isEmpty()) {
-                providers = new LinkedList<IdentityProvider>(orderedSet);
+                providers = new LinkedList<>(orderedSet);
                 displaySocial = true;
             }
         }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java
index e730c14..b2d6b1d 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java
@@ -28,7 +28,7 @@ import java.util.Map;
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.jboss.logging.Logger;
-import org.keycloak.models.UserModel;
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -38,12 +38,12 @@ public class ProfileBean {
 
     private static final Logger logger = Logger.getLogger(ProfileBean.class);
 
-    private UserModel user;
+    private UpdateProfileContext user;
     private MultivaluedMap<String, String> formData;
 
     private final Map<String, String> attributes = new HashMap<>();
 
-    public ProfileBean(UserModel user, MultivaluedMap<String, String> formData) {
+    public ProfileBean(UpdateProfileContext user, MultivaluedMap<String, String> formData) {
         this.user = user;
         this.formData = formData;
 
@@ -70,6 +70,10 @@ public class ProfileBean {
 
     }
 
+    public boolean isEditUsernameAllowed() {
+        return user.isEditUsernameAllowed();
+    }
+
     public String getUsername() { return formData != null ? formData.getFirst("username") : user.getUsername(); }
 
     public String getFirstName() {
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index 25ebc83..ff83c43 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -90,6 +90,10 @@ public class UrlBean {
         return Urls.loginActionEmailVerification(baseURI, realm).toString();
     }
 
+    public String getFirstBrokerLoginUrl() {
+        return Urls.firstBrokerLoginProcessor(baseURI, realm).toString();
+    }
+
     public String getOauthAction() {
         if (this.actionuri != null) {
             return this.actionuri.getPath();
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
index 785b186..e63f49f 100644
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
@@ -17,6 +17,10 @@ public class Templates {
                 return "login-config-totp.ftl";
             case LOGIN_VERIFY_EMAIL:
                 return "login-verify-email.ftl";
+            case LOGIN_IDP_LINK_CONFIRM:
+                return "login-idp-link-confirm.ftl";
+            case LOGIN_IDP_LINK_EMAIL:
+                return "login-idp-link-email.ftl";
             case OAUTH_GRANT:
                 return "login-oauth-grant.ftl";
             case LOGIN_RESET_PASSWORD:
diff --git a/integration/jetty/jetty-adapter-spi/pom.xml b/integration/jetty/jetty-adapter-spi/pom.xml
index 2072ba3..bbe9e88 100755
--- a/integration/jetty/jetty-adapter-spi/pom.xml
+++ b/integration/jetty/jetty-adapter-spi/pom.xml
@@ -12,7 +12,7 @@
 	<artifactId>keycloak-jetty-adapter-spi</artifactId>
 	<name>Keycloak Jetty Adapter SPI</name>
     <properties>
-        <jetty9.version>8.1.16.v20140903</jetty9.version>
+        <jetty9.version>8.1.17.v20150415</jetty9.version>
         <keycloak.osgi.export>
             org.keycloak.adapters.jetty.spi.*
         </keycloak.osgi.export>
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
index 60d413c..a517416 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
@@ -3,10 +3,7 @@ package org.keycloak.adapters.springsecurity;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
-import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
 import org.springframework.core.io.Resource;
 
 import java.io.FileNotFoundException;
@@ -20,16 +17,17 @@ import java.io.IOException;
  * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
  * @version $Revision: 1 $
  */
-public class AdapterDeploymentContextBean implements ApplicationContextAware, InitializingBean {
+public class AdapterDeploymentContextBean implements InitializingBean {
 
-    private static final String KEYCLOAK_CONFIG_FILE = "keycloak.json";
-    private static final String KEYCLOAK_CONFIG_WEB_RESOURCE = "WEB-INF/" + KEYCLOAK_CONFIG_FILE;
-    private static final String KEYCLOAK_CONFIG_CLASSPATH_RESOURCE = "classpath:" + KEYCLOAK_CONFIG_FILE;
+    private final Resource keycloakConfigFileResource;
 
-    private ApplicationContext applicationContext;
     private AdapterDeploymentContext deploymentContext;
     private KeycloakDeployment deployment;
 
+    public AdapterDeploymentContextBean(Resource keycloakConfigFileResource) {
+        this.keycloakConfigFileResource = keycloakConfigFileResource;
+    }
+
     @Override
     public void afterPropertiesSet() throws Exception {
         this.deployment = loadKeycloakDeployment();
@@ -38,17 +36,12 @@ public class AdapterDeploymentContextBean implements ApplicationContextAware, In
 
     private KeycloakDeployment loadKeycloakDeployment() throws IOException {
 
-        Resource resource = applicationContext.getResource(KEYCLOAK_CONFIG_WEB_RESOURCE);
-
-        if (!resource.isReadable()) {
-            resource=  applicationContext.getResource(KEYCLOAK_CONFIG_CLASSPATH_RESOURCE);
-        }
-
-        if (!resource.isReadable()) {
-            throw new FileNotFoundException(String.format("Unable to locate Keycloak from %s or %s", KEYCLOAK_CONFIG_WEB_RESOURCE, KEYCLOAK_CONFIG_CLASSPATH_RESOURCE));
+        if (!keycloakConfigFileResource.isReadable()) {
+            throw new FileNotFoundException(String.format("Unable to locate Keycloak configuration file: %s",
+                    keycloakConfigFileResource.getFilename()));
         }
 
-        return KeycloakDeploymentBuilder.build(resource.getInputStream());
+        return KeycloakDeploymentBuilder.build(keycloakConfigFileResource.getInputStream());
     }
 
     /**
@@ -68,9 +61,4 @@ public class AdapterDeploymentContextBean implements ApplicationContextAware, In
     public KeycloakDeployment getDeployment() {
         return deployment;
     }
-
-    @Override
-    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        this.applicationContext = applicationContext;
-    }
 }
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
index 6cbcbe6..b5ef665 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
@@ -8,7 +8,9 @@ import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcess
 import org.keycloak.adapters.springsecurity.filter.KeycloakCsrfRequestMatcher;
 import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
 import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.Resource;
 import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -26,19 +28,20 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
  *
  * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
  * @version $Revision: 1 $
- *
  * @see EnableWebSecurity
  * @see EnableWebMvcSecurity
  */
 public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
 
+    @Value("${keycloak.configurationFile:WEB-INF/keycloak.json}")
+    private Resource keycloakConfigFileResource;
+
     @Bean
     protected AdapterDeploymentContextBean adapterDeploymentContextBean() {
-        return new AdapterDeploymentContextBean();
+        return new AdapterDeploymentContextBean(keycloakConfigFileResource);
     }
 
-    protected AuthenticationEntryPoint authenticationEntryPoint()
-    {
+    protected AuthenticationEntryPoint authenticationEntryPoint() {
         return new KeycloakAuthenticationEntryPoint();
     }
 
@@ -48,7 +51,7 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
 
     @Bean
     protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
-        KeycloakAuthenticationProcessingFilter filter  = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
+        KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
         filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
         return filter;
     }
@@ -64,7 +67,7 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
 
     @Bean
     protected HttpSessionManager httpSessionManager() {
-        return  new HttpSessionManager();
+        return new HttpSessionManager();
     }
 
     protected KeycloakLogoutHandler keycloakLogoutHandler() {
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java
new file mode 100644
index 0000000..3510db7
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java
@@ -0,0 +1,56 @@
+package org.keycloak.adapters.springsecurity;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import java.io.FileNotFoundException;
+
+import static org.junit.Assert.assertNotNull;
+
+public class AdapterDeploymentContextBeanTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private AdapterDeploymentContextBean adapterDeploymentContextBean;
+
+    @Test
+    public void should_create_deployment_and_deployment_context() throws Exception {
+
+        //given:
+        adapterDeploymentContextBean = new AdapterDeploymentContextBean(getCorrectResource());
+
+        //when:
+        adapterDeploymentContextBean.afterPropertiesSet();
+
+        //then
+        assertNotNull(adapterDeploymentContextBean.getDeployment());
+        assertNotNull(adapterDeploymentContextBean.getDeploymentContext());
+    }
+
+    private Resource getCorrectResource() {
+        return new ClassPathResource("keycloak.json");
+    }
+
+    @Test
+    public void should_throw_exception_when_configuration_file_was_not_found() throws Exception {
+
+        //given:
+        adapterDeploymentContextBean = new AdapterDeploymentContextBean(getEmptyResource());
+
+        //then:
+        expectedException.expect(FileNotFoundException.class);
+        expectedException.expectMessage("Unable to locate Keycloak configuration file: no-file.json");
+
+        //when:
+        adapterDeploymentContextBean.afterPropertiesSet();
+    }
+
+    private Resource getEmptyResource() {
+        return new ClassPathResource("no-file.json");
+    }
+}
diff --git a/integration/spring-security/src/test/resources/keycloak.json b/integration/spring-security/src/test/resources/keycloak.json
new file mode 100644
index 0000000..61e0f93
--- /dev/null
+++ b/integration/spring-security/src/test/resources/keycloak.json
@@ -0,0 +1,10 @@
+{
+    "realm": "spring-security",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh65Gqi3BSaVe12JHlqChWm8WscICrj46MVqmRoO9FCmqbxEpCQhE1RLjW+GDyc3YdXW3xqUQ3AZxDkTmN1h6BWkhdxPLzA4EnwgWmGurhyJlUF9Id2tKns0jbC+Z7kIb2LcOiKHKL7mRb3q7EtWubNnrvunv8fx+WeXGaQoGEVQIDAQAB",
+    "auth-server-url": "http://localhost:8080/auth",
+    "ssl-required": "external",
+    "resource": "some-resource",
+    "credentials": {
+        "secret": "a9c3501e-20dd-4277-8a7b-351063848446"
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 8977def..3ecc51c 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -23,5 +23,6 @@ public interface Constants {
     // 30 days
     int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
 
-    public static final String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
+    String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
+    String KEY = "key";
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
index c4cdd62..f92f1d2 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
@@ -30,11 +30,11 @@ public class IdentityProviderEntity {
     private String providerId;
     private String name;
     private boolean enabled;
-    private String updateProfileFirstLoginMode;
     private boolean trustEmail;
     private boolean storeToken;
     protected boolean addReadTokenRoleOnCreate;
     private boolean authenticateByDefault;
+    private String firstBrokerLoginFlowId;
 
     private Map<String, String> config = new HashMap<String, String>();
 
@@ -62,14 +62,6 @@ public class IdentityProviderEntity {
         this.enabled = enabled;
     }
 
-    public String getUpdateProfileFirstLoginMode() {
-        return updateProfileFirstLoginMode;
-    }
-
-    public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
-        this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
-    }
-
     public boolean isAuthenticateByDefault() {
         return authenticateByDefault;
     }
@@ -78,6 +70,14 @@ public class IdentityProviderEntity {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowId() {
+        return firstBrokerLoginFlowId;
+    }
+
+    public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
+        this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
+    }
+
     public boolean isStoreToken() {
         return this.storeToken;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
index fa3eb71..862f723 100755
--- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -45,14 +45,6 @@ public class IdentityProviderModel implements Serializable {
     private String providerId;
 
     private boolean enabled;
-
-    /**
-     * For possible values see {@link IdentityProviderRepresentation#getUpdateProfileFirstLoginMode()}
-     * @see IdentityProviderRepresentation#UPFLM_ON
-     * @see IdentityProviderRepresentation#UPFLM_MISSING
-     * @see IdentityProviderRepresentation#UPFLM_OFF
-     */
-    protected String updateProfileFirstLoginMode = IdentityProviderRepresentation.UPFLM_ON;
     
     private boolean trustEmail;
 
@@ -64,6 +56,8 @@ public class IdentityProviderModel implements Serializable {
      */
     private boolean authenticateByDefault;
 
+    private String firstBrokerLoginFlowId;
+
     /**
      * <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
      * in the map are understood by the identity provider implementation.</p>
@@ -79,11 +73,11 @@ public class IdentityProviderModel implements Serializable {
         this.alias = model.getAlias();
         this.config = new HashMap<String, String>(model.getConfig());
         this.enabled = model.isEnabled();
-        this.updateProfileFirstLoginMode = model.getUpdateProfileFirstLoginMode();
         this.trustEmail = model.isTrustEmail();
         this.storeToken = model.isStoreToken();
         this.authenticateByDefault = model.isAuthenticateByDefault();
         this.addReadTokenRoleOnCreate = model.addReadTokenRoleOnCreate;
+        this.firstBrokerLoginFlowId = model.getFirstBrokerLoginFlowId();
     }
 
     public String getInternalId() {
@@ -118,20 +112,6 @@ public class IdentityProviderModel implements Serializable {
         this.enabled = enabled;
     }
 
-    /**
-     * @see IdentityProviderRepresentation#getUpdateProfileFirstLoginMode() 
-     */
-    public String getUpdateProfileFirstLoginMode() {
-        return updateProfileFirstLoginMode;
-    }
-
-    /**
-     * @see IdentityProviderRepresentation#setUpdateProfileFirstLoginMode(String) 
-     */
-    public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
-        this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
-    }
-
     public boolean isStoreToken() {
         return this.storeToken;
     }
@@ -148,6 +128,14 @@ public class IdentityProviderModel implements Serializable {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowId() {
+        return firstBrokerLoginFlowId;
+    }
+
+    public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
+        this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 9992af3..a874f5e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -1,9 +1,14 @@
 package org.keycloak.models.utils;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -19,6 +24,9 @@ public class DefaultAuthenticationFlows {
     public static final String LOGIN_FORMS_FLOW = "forms";
 
     public static final String CLIENT_AUTHENTICATION_FLOW = "clients";
+    public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login";
+
+    public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config";
 
     public static void addFlows(RealmModel realm) {
         if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
@@ -26,6 +34,7 @@ public class DefaultAuthenticationFlows {
         if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
         if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
         if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
+        if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
     }
     public static void migrateFlows(RealmModel realm) {
         if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
@@ -33,6 +42,7 @@ public class DefaultAuthenticationFlows {
         if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
         if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
         if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
+        if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
     }
 
     public static void registrationFlow(RealmModel realm) {
@@ -309,4 +319,115 @@ public class DefaultAuthenticationFlows {
         execution.setAuthenticatorFlow(false);
         realm.addAuthenticatorExecution(execution);
     }
+
+    public static void firstBrokerLoginFlow(RealmModel realm) {
+        AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
+        firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW);
+        firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account");
+        firstBrokerLogin.setProviderId("basic-flow");
+        firstBrokerLogin.setTopLevel(true);
+        firstBrokerLogin.setBuiltIn(true);
+        firstBrokerLogin = realm.addAuthenticationFlow(firstBrokerLogin);
+
+        AuthenticatorConfigModel reviewProfileConfig = new AuthenticatorConfigModel();
+        reviewProfileConfig.setAlias(IDP_REVIEW_PROFILE_CONFIG_ALIAS);
+        Map<String, String> config = new HashMap<>();
+        config.put("update.profile.on.first.login", IdentityProviderRepresentation.UPFLM_MISSING);
+        reviewProfileConfig.setConfig(config);
+        reviewProfileConfig = realm.addAuthenticatorConfig(reviewProfileConfig);
+
+        AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(firstBrokerLogin.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("idp-review-profile");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+        execution.setAuthenticatorConfig(reviewProfileConfig.getId());
+        realm.addAuthenticatorExecution(execution);
+
+
+        AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel();
+        createUserIfUniqueConfig.setAlias("create unique user config");
+        config = new HashMap<>();
+        config.put("require.password.update.after.registration", "false");
+        createUserIfUniqueConfig.setConfig(config);
+        createUserIfUniqueConfig = realm.addAuthenticatorConfig(createUserIfUniqueConfig);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(firstBrokerLogin.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setAuthenticator("idp-create-user-if-unique");
+        execution.setPriority(20);
+        execution.setAuthenticatorFlow(false);
+        execution.setAuthenticatorConfig(createUserIfUniqueConfig.getId());
+        realm.addAuthenticatorExecution(execution);
+
+
+        AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel();
+        linkExistingAccountFlow.setTopLevel(false);
+        linkExistingAccountFlow.setBuiltIn(true);
+        linkExistingAccountFlow.setAlias("Handle Existing Account");
+        linkExistingAccountFlow.setDescription("Handle what to do if there is existing account with same email/username like authenticated identity provider");
+        linkExistingAccountFlow.setProviderId("basic-flow");
+        linkExistingAccountFlow = realm.addAuthenticationFlow(linkExistingAccountFlow);
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(firstBrokerLogin.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setFlowId(linkExistingAccountFlow.getId());
+        execution.setPriority(30);
+        execution.setAuthenticatorFlow(true);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(linkExistingAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("idp-confirm-link");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(linkExistingAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setAuthenticator("idp-email-verification");
+        execution.setPriority(20);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        AuthenticationFlowModel verifyByReauthenticationAccountFlow = new AuthenticationFlowModel();
+        verifyByReauthenticationAccountFlow.setTopLevel(false);
+        verifyByReauthenticationAccountFlow.setBuiltIn(true);
+        verifyByReauthenticationAccountFlow.setAlias("Verify Existing Account by Re-authentication");
+        verifyByReauthenticationAccountFlow.setDescription("Reauthentication of existing account");
+        verifyByReauthenticationAccountFlow.setProviderId("basic-flow");
+        verifyByReauthenticationAccountFlow = realm.addAuthenticationFlow(verifyByReauthenticationAccountFlow);
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(linkExistingAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setFlowId(verifyByReauthenticationAccountFlow.getId());
+        execution.setPriority(30);
+        execution.setAuthenticatorFlow(true);
+        realm.addAuthenticatorExecution(execution);
+
+        // password + otp
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("idp-username-password-form");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
+        // TODO: read the requirement from browser authenticator
+//        if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
+//            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+//        }
+        execution.setAuthenticator("auth-otp-form");
+        execution.setPriority(20);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 8244467..c2fd73e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.GroupModel;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
@@ -199,9 +200,9 @@ public final class KeycloakModelUtils {
     /**
      * Deep search if given role is descendant of composite role
      *
-     * @param role role to check
+     * @param role      role to check
      * @param composite composite role
-     * @param visited set of already visited roles (used for recursion)
+     * @param visited   set of already visited roles (used for recursion)
      * @return true if "role" is descendant of "composite"
      */
     public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
@@ -219,14 +220,14 @@ public final class KeycloakModelUtils {
     /**
      * Try to find user by given username. If it fails, then fallback to find him by email
      *
-     * @param realm realm
+     * @param realm    realm
      * @param username username or email of user
      * @return found user
      */
     public static UserModel findUserByNameOrEmail(KeycloakSession session, RealmModel realm, String username) {
         UserModel user = session.users().getUserByUsername(username, realm);
         if (user == null && username.contains("@")) {
-            user =  session.users().getUserByEmail(username, realm);
+            user = session.users().getUserByEmail(username, realm);
         }
         return user;
     }
@@ -266,7 +267,6 @@ public final class KeycloakModelUtils {
     }
 
     /**
-     *
      * @param roles
      * @param targetRole
      * @return true if targetRole is in roles (directly or indirectly via composite role)
@@ -303,8 +303,8 @@ public final class KeycloakModelUtils {
     /**
      * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list.
      *
-     * @param displayName to check for duplications
-     * @param myProvider provider, which is excluded from the list (if present)
+     * @param displayName         to check for duplications
+     * @param myProvider          provider, which is excluded from the list (if present)
      * @param federationProviders
      * @throws ModelDuplicateException if there is other provider with same displayName
      */
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index b7f0988..c62d96e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -257,7 +257,7 @@ public class ModelToRepresentation {
         }
 
         for (IdentityProviderModel provider : realm.getIdentityProviders()) {
-            rep.addIdentityProvider(toRepresentation(provider));
+            rep.addIdentityProvider(toRepresentation(realm, provider));
         }
 
         for (IdentityProviderMapperModel mapper : realm.getIdentityProviderMappers()) {
@@ -436,7 +436,7 @@ public class ModelToRepresentation {
         return rep;
     }
 
-    public static IdentityProviderRepresentation toRepresentation(IdentityProviderModel identityProviderModel) {
+    public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
         IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation();
 
         providerRep.setInternalId(identityProviderModel.getInternalId());
@@ -444,12 +444,20 @@ public class ModelToRepresentation {
         providerRep.setAlias(identityProviderModel.getAlias());
         providerRep.setEnabled(identityProviderModel.isEnabled());
         providerRep.setStoreToken(identityProviderModel.isStoreToken());
-        providerRep.setUpdateProfileFirstLoginMode(identityProviderModel.getUpdateProfileFirstLoginMode());
         providerRep.setTrustEmail(identityProviderModel.isTrustEmail());
         providerRep.setAuthenticateByDefault(identityProviderModel.isAuthenticateByDefault());
         providerRep.setConfig(identityProviderModel.getConfig());
         providerRep.setAddReadTokenRoleOnCreate(identityProviderModel.isAddReadTokenRoleOnCreate());
 
+        String firstBrokerLoginFlowId = identityProviderModel.getFirstBrokerLoginFlowId();
+        if (firstBrokerLoginFlowId != null) {
+            AuthenticationFlowModel flow = realm.getAuthenticationFlowById(firstBrokerLoginFlowId);
+            if (flow == null) {
+                throw new ModelException("Couldn't find authentication flow with id " + firstBrokerLoginFlowId);
+            }
+            providerRep.setFirstBrokerLoginFlowAlias(flow.getAlias());
+        }
+
         return providerRep;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 85b4ac0..a31d355 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -164,6 +164,16 @@ public class RepresentationToModel {
         if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
         else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
 
+        importAuthenticationFlows(newRealm, rep);
+        if (rep.getRequiredActions() != null) {
+            for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
+                RequiredActionProviderModel model = toModel(action);
+                newRealm.addRequiredActionProvider(model);
+            }
+        } else {
+            DefaultRequiredActions.addActions(newRealm);
+        }
+
         importIdentityProviders(rep, newRealm);
         importIdentityProviderMappers(rep, newRealm);
 
@@ -318,16 +328,6 @@ public class RepresentationToModel {
         if(rep.getDefaultLocale() != null){
             newRealm.setDefaultLocale(rep.getDefaultLocale());
         }
-
-        importAuthenticationFlows(newRealm, rep);
-        if (rep.getRequiredActions() != null) {
-            for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
-                RequiredActionProviderModel model = toModel(action);
-                newRealm.addRequiredActionProvider(model);
-            }
-        } else {
-            DefaultRequiredActions.addActions(newRealm);
-        }
     }
 
     public static void importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) {
@@ -1062,7 +1062,7 @@ public class RepresentationToModel {
     private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) {
         if (rep.getIdentityProviders() != null) {
             for (IdentityProviderRepresentation representation : rep.getIdentityProviders()) {
-                newRealm.addIdentityProvider(toModel(representation));
+                newRealm.addIdentityProvider(toModel(newRealm, representation));
             }
         }
     }
@@ -1073,21 +1073,31 @@ public class RepresentationToModel {
             }
         }
     }
-   public static IdentityProviderModel toModel(IdentityProviderRepresentation representation) {
+   public static IdentityProviderModel toModel(RealmModel realm, IdentityProviderRepresentation representation) {
         IdentityProviderModel identityProviderModel = new IdentityProviderModel();
 
         identityProviderModel.setInternalId(representation.getInternalId());
         identityProviderModel.setAlias(representation.getAlias());
         identityProviderModel.setProviderId(representation.getProviderId());
         identityProviderModel.setEnabled(representation.isEnabled());
-        identityProviderModel.setUpdateProfileFirstLoginMode(representation.getUpdateProfileFirstLoginMode());
         identityProviderModel.setTrustEmail(representation.isTrustEmail());
         identityProviderModel.setAuthenticateByDefault(representation.isAuthenticateByDefault());
         identityProviderModel.setStoreToken(representation.isStoreToken());
         identityProviderModel.setAddReadTokenRoleOnCreate(representation.isAddReadTokenRoleOnCreate());
         identityProviderModel.setConfig(representation.getConfig());
 
-        return identityProviderModel;
+        String flowAlias = representation.getFirstBrokerLoginFlowAlias();
+        if (flowAlias == null) {
+            flowAlias = DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW;
+        }
+
+       AuthenticationFlowModel flowModel = realm.getFlowByAlias(flowAlias);
+       if (flowModel == null) {
+           throw new ModelException("No available authentication flow with alias: " + flowAlias);
+       }
+       identityProviderModel.setFirstBrokerLoginFlowId(flowModel.getId());
+
+       return identityProviderModel;
     }
 
     public static ProtocolMapperModel toModel(ProtocolMapperRepresentation rep) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
index eeef707..6bbc31c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
@@ -11,6 +11,7 @@ import javax.persistence.ManyToOne;
 import javax.persistence.MapKeyColumn;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import java.util.Map;
 
@@ -41,9 +42,6 @@ public class IdentityProviderEntity {
     @Column(name="ENABLED")
     private boolean enabled;
 
-    @Column(name = "UPDATE_PROFILE_FIRST_LGN_MD")
-    private String updateProfileFirstLoginMode;
-
     @Column(name = "TRUST_EMAIL")
     private boolean trustEmail;
 
@@ -56,6 +54,9 @@ public class IdentityProviderEntity {
     @Column(name="AUTHENTICATE_BY_DEFAULT")
     private boolean authenticateByDefault;
 
+    @Column(name="FIRST_BROKER_LOGIN_FLOW_ID")
+    private String firstBrokerLoginFlowId;
+
     @ElementCollection
     @MapKeyColumn(name="NAME")
     @Column(name="VALUE", columnDefinition = "TEXT")
@@ -102,14 +103,6 @@ public class IdentityProviderEntity {
         this.enabled = enabled;
     }
 
-    public String getUpdateProfileFirstLoginMode() {
-        return updateProfileFirstLoginMode;
-    }
-
-    public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
-        this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
-    }
-
     public boolean isStoreToken() {
         return this.storeToken;
     }
@@ -126,6 +119,14 @@ public class IdentityProviderEntity {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowId() {
+        return firstBrokerLoginFlowId;
+    }
+
+    public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
+        this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 95b75d9..78de054 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -120,7 +120,7 @@ public class RealmEntity {
     Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
-    @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") })
+    @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name = "USERFEDERATIONPROVIDERS_ID") })
     List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 437867e..ddf9244 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -30,7 +30,7 @@ import java.util.Collection;
 
 public class RoleEntity {
     @Id
-    @Column(name="id", length = 36)
+    @Column(name="ID", length = 36)
     private String id;
 
     @Column(name = "NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 2da1641..5051ade 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -77,7 +77,7 @@ public class UserEntity {
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
     protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
 
-    @Column(name="federation_link")
+    @Column(name="FEDERATION_LINK")
     protected String federationLink;
 
     @Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
index 22370f3..259c974 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
@@ -35,7 +35,7 @@ public class UserFederationProviderEntity {
 
     @ElementCollection
     @MapKeyColumn(name="name")
-    @Column(name="value")
+    @Column(name="VALUE")
     @CollectionTable(name="USER_FEDERATION_CONFIG", joinColumns={ @JoinColumn(name="USER_FEDERATION_PROVIDER_ID") })
     private Map<String, String> config;
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index a58c413..4a26cd8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1218,9 +1218,9 @@ public class RealmAdapter implements RealmModel {
             identityProviderModel.setInternalId(entity.getInternalId());
             identityProviderModel.setConfig(entity.getConfig());
             identityProviderModel.setEnabled(entity.isEnabled());
-            identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode());
             identityProviderModel.setTrustEmail(entity.isTrustEmail());
             identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+            identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
             identityProviderModel.setStoreToken(entity.isStoreToken());
             identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
 
@@ -1251,9 +1251,9 @@ public class RealmAdapter implements RealmModel {
         entity.setEnabled(identityProvider.isEnabled());
         entity.setStoreToken(identityProvider.isStoreToken());
         entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
-        entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
         entity.setTrustEmail(identityProvider.isTrustEmail());
         entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+        entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
         entity.setConfig(identityProvider.getConfig());
 
         realm.addIdentityProvider(entity);
@@ -1278,9 +1278,9 @@ public class RealmAdapter implements RealmModel {
             if (entity.getInternalId().equals(identityProvider.getInternalId())) {
                 entity.setAlias(identityProvider.getAlias());
                 entity.setEnabled(identityProvider.isEnabled());
-                entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
                 entity.setTrustEmail(identityProvider.isTrustEmail());
                 entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+                entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
                 entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
                 entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 442a0b9..59bc553 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -898,9 +898,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             identityProviderModel.setInternalId(entity.getInternalId());
             identityProviderModel.setConfig(entity.getConfig());
             identityProviderModel.setEnabled(entity.isEnabled());
-            identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode());
             identityProviderModel.setTrustEmail(entity.isTrustEmail());
             identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+            identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
             identityProviderModel.setStoreToken(entity.isStoreToken());
             identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
 
@@ -929,11 +929,11 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         entity.setAlias(identityProvider.getAlias());
         entity.setProviderId(identityProvider.getProviderId());
         entity.setEnabled(identityProvider.isEnabled());
-        entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
         entity.setTrustEmail(identityProvider.isTrustEmail());
         entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
         entity.setStoreToken(identityProvider.isStoreToken());
         entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+        entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
         entity.setConfig(identityProvider.getConfig());
 
         realm.getIdentityProviders().add(entity);
@@ -957,9 +957,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             if (entity.getInternalId().equals(identityProvider.getInternalId())) {
                 entity.setAlias(identityProvider.getAlias());
                 entity.setEnabled(identityProvider.isEnabled());
-                entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
                 entity.setTrustEmail(identityProvider.isTrustEmail());
                 entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+                entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
                 entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
                 entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
diff --git a/saml/saml-core/pom.xml b/saml/saml-core/pom.xml
index 552f7be..5a1c01b 100755
--- a/saml/saml-core/pom.xml
+++ b/saml/saml-core/pom.xml
@@ -32,11 +32,6 @@
             <groupId>org.apache.santuario</groupId>
             <artifactId>xmlsec</artifactId>
         </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 3b5d545..dc25f17 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -76,6 +76,11 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
      * @throws {@link IllegalArgumentException} when the configStream is null
      */
     public Object parse(InputStream configStream) throws ParsingException {
+        XMLEventReader xmlEventReader = createEventReader(configStream);
+        return parse(xmlEventReader);
+    }
+
+    public XMLEventReader createEventReader(InputStream configStream) throws ParsingException {
         if (configStream == null)
             throw logger.nullArgumentError("InputStream");
 
@@ -105,7 +110,7 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
             throw logger.parserException(e);
         }
 
-        return parse(xmlEventReader);
+        return xmlEventReader;
     }
 
     private ClassLoader getTCCL() {
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
index 9157982..3db64c2 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
@@ -180,36 +180,6 @@ public class StringUtil {
         return map;
     }
 
-    /**
-     * Given a masked password {@link String}, decode it
-     *
-     * @param maskedString a password string that is masked
-     * @param salt Salt
-     * @param iterationCount Iteration Count
-     *
-     * @return Decoded String
-     *
-     * @throws Exception
-     */
-    public static String decode(String maskedString, String salt, int iterationCount) throws Exception {
-        String pbeAlgo = PicketLinkCommonConstants.PBE_ALGORITHM;
-        if (maskedString.startsWith(PicketLinkCommonConstants.PASS_MASK_PREFIX)) {
-            // Create the PBE secret key
-            SecretKeyFactory factory = SecretKeyFactory.getInstance(pbeAlgo);
-
-            char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
-            PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(), iterationCount);
-            PBEKeySpec keySpec = new PBEKeySpec(password);
-            SecretKey cipherKey = factory.generateSecret(keySpec);
-
-            maskedString = maskedString.substring(PicketLinkCommonConstants.PASS_MASK_PREFIX.length());
-            String decodedValue = PBEUtils.decode64(maskedString, pbeAlgo, cipherKey, cipherSpec);
-
-            maskedString = decodedValue;
-        }
-        return maskedString;
-    }
-
     public static String[] split(String toSplit, String delimiter) {
         if (delimiter.length() != 1) {
             throw new IllegalArgumentException("Delimiter can only be one character in length");
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
index af646e2..23a90d3 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
@@ -136,7 +136,7 @@ public class SAMLAssertionWriter extends BaseWriter {
         if (statements != null) {
             for (StatementAbstractType statement : statements) {
                 if (statement instanceof AuthnStatementType) {
-                    write((AuthnStatementType) statement);
+                    write((AuthnStatementType) statement, false);
                 } else if (statement instanceof AttributeStatementType) {
                     write((AttributeStatementType) statement);
                 } else
@@ -188,8 +188,12 @@ public class SAMLAssertionWriter extends BaseWriter {
      *
      * @throws ProcessingException
      */
-    public void write(AuthnStatementType authnStatement) throws ProcessingException {
+    public void write(AuthnStatementType authnStatement, boolean includeNamespace) throws ProcessingException {
         StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.AUTHN_STATEMENT.get(), ASSERTION_NSURI.get());
+        if (includeNamespace) {
+            StaxUtil.writeNameSpace(writer, ASSERTION_PREFIX, ASSERTION_NSURI.get());
+            StaxUtil.writeDefaultNameSpace(writer, ASSERTION_NSURI.get());
+        }
 
         XMLGregorianCalendar authnInstant = authnStatement.getAuthnInstant();
         if (authnInstant != null) {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
index 8fd0faa..db3db37 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
@@ -22,5 +22,9 @@ public enum AuthenticationFlowError {
     CLIENT_NOT_FOUND,
     CLIENT_DISABLED,
     CLIENT_CREDENTIALS_SETUP_REQUIRED,
-    INVALID_CLIENT_CREDENTIALS
+    INVALID_CLIENT_CREDENTIALS,
+
+    IDENTITY_PROVIDER_NOT_FOUND,
+    IDENTITY_PROVIDER_DISABLED,
+    IDENTITY_PROVIDER_ERROR
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
new file mode 100644
index 0000000..b7c7663
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
@@ -0,0 +1,118 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.events.Errors;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractIdpAuthenticator implements Authenticator {
+
+    // The clientSession note encapsulating all the BrokeredIdentityContext info. When this note is in clientSession, we know that firstBrokerLogin flow is in progress
+    public static final String BROKERED_CONTEXT_NOTE = "BROKERED_CONTEXT";
+
+    // The clientSession note with all the info about existing user
+    public static final String EXISTING_USER_INFO = "EXISTING_USER_INFO";
+
+    // The clientSession note flag to indicate that email provided by identityProvider was changed on updateProfile page
+    public static final String UPDATE_PROFILE_EMAIL_CHANGED = "UPDATE_PROFILE_EMAIL_CHANGED";
+
+    // The clientSession note flag to indicate if re-authentication after first broker login happened in different browser window. This can happen for example during email verification
+    public static final String IS_DIFFERENT_BROWSER = "IS_DIFFERENT_BROWSER";
+
+    // The clientSession note flag to indicate that updateProfile page will be always displayed even if "updateProfileOnFirstLogin" is off
+    public static final String ENFORCE_UPDATE_PROFILE = "ENFORCE_UPDATE_PROFILE";
+
+    // clientSession.note flag specifies if we imported new user to keycloak (true) or we just linked to an existing keycloak user (false)
+    public static final String BROKER_REGISTERED_NEW_USER = "BROKER_REGISTERED_NEW_USER";
+
+
+    @Override
+    public void authenticate(AuthenticationFlowContext context) {
+        ClientSessionModel clientSession = context.getClientSession();
+
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+        if (serializedCtx == null) {
+            throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(context.getSession(), clientSession);
+
+        if (!brokerContext.getIdpConfig().isEnabled()) {
+            sendFailureChallenge(context, Errors.IDENTITY_PROVIDER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+
+        authenticateImpl(context, serializedCtx, brokerContext);
+    }
+
+    @Override
+    public void action(AuthenticationFlowContext context) {
+        ClientSessionModel clientSession = context.getClientSession();
+
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+        if (serializedCtx == null) {
+            throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(context.getSession(), clientSession);
+
+        if (!brokerContext.getIdpConfig().isEnabled()) {
+            sendFailureChallenge(context, Errors.IDENTITY_PROVIDER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+
+        actionImpl(context, serializedCtx, brokerContext);
+    }
+
+    protected abstract void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext);
+    protected abstract void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext);
+
+    protected void sendFailureChallenge(AuthenticationFlowContext context, String eventError, String errorMessage, AuthenticationFlowError flowError) {
+        context.getEvent().user(context.getUser())
+                .error(eventError);
+        Response challengeResponse = context.form()
+                .setError(errorMessage)
+                .createErrorPage();
+        context.failureChallenge(flowError, challengeResponse);
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    public static UserModel getExistingUser(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+        String existingUserId = clientSession.getNote(EXISTING_USER_INFO);
+        if (existingUserId == null) {
+            throw new AuthenticationFlowException("Unexpected state. There is no existing duplicated user identified in ClientSession",
+                    AuthenticationFlowError.INTERNAL_ERROR);
+        }
+
+        ExistingUserInfo duplication = ExistingUserInfo.deserialize(existingUserId);
+
+        UserModel existingUser = session.users().getUserById(duplication.getExistingUserId(), realm);
+        if (existingUser == null) {
+            throw new AuthenticationFlowException("User with ID '" + existingUserId + "' not found.", AuthenticationFlowError.INVALID_USER);
+        }
+
+        if (!existingUser.isEnabled()) {
+            throw new AuthenticationFlowException("User with ID '" + existingUserId + "', username '" + existingUser.getUsername() + "' disabled.", AuthenticationFlowError.USER_DISABLED);
+        }
+
+        return existingUser;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
new file mode 100644
index 0000000..6d6281d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
@@ -0,0 +1,73 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpConfirmLinkAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpConfirmLinkAuthenticator.class);
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        ClientSessionModel clientSession = context.getClientSession();
+
+        String existingUserInfo = clientSession.getNote(EXISTING_USER_INFO);
+        if (existingUserInfo == null) {
+            logger.warnf("No duplication detected.");
+            context.attempted();
+            return;
+        }
+
+        ExistingUserInfo duplicationInfo = ExistingUserInfo.deserialize(existingUserInfo);
+        Response challenge = context.form()
+                .setStatus(Response.Status.OK)
+                .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
+                .setError(Messages.FEDERATED_IDENTITY_CONFIRM_LINK_MESSAGE, duplicationInfo.getDuplicateAttributeName(), duplicationInfo.getDuplicateAttributeValue())
+                .createIdpLinkConfirmLinkPage();
+        context.challenge(challenge);
+    }
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+
+        String action = formData.getFirst("submitAction");
+        if (action != null && action.equals("updateProfile")) {
+            context.getClientSession().setNote(ENFORCE_UPDATE_PROFILE, "true");
+            context.getClientSession().removeNote(EXISTING_USER_INFO);
+            context.resetFlow();
+        } else if (action != null && action.equals("linkAccount")) {
+            context.success();
+        } else {
+            throw new AuthenticationFlowException("Unknown action: " + action,
+                    AuthenticationFlowError.INTERNAL_ERROR);
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticatorFactory.java
new file mode 100644
index 0000000..930a8bf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticatorFactory.java
@@ -0,0 +1,86 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpConfirmLinkAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-confirm-link";
+    static IdpConfirmLinkAuthenticator SINGLETON = new IdpConfirmLinkAuthenticator();
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "confirmLink";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Confirm link existing account";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Show the form where user confirms if he wants to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
new file mode 100644
index 0000000..ffb2300
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
@@ -0,0 +1,113 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpCreateUserIfUniqueAuthenticator.class);
+
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+    }
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+
+        KeycloakSession session = context.getSession();
+        RealmModel realm = context.getRealm();
+
+        if (context.getClientSession().getNote(EXISTING_USER_INFO) != null) {
+            context.attempted();
+            return;
+        }
+
+        ExistingUserInfo duplication = checkExistingUser(context, serializedCtx, brokerContext);
+
+        if (duplication == null) {
+            logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .",
+                    brokerContext.getModelUsername(), brokerContext.getIdpConfig().getAlias());
+
+            UserModel federatedUser = session.users().addUser(realm, brokerContext.getModelUsername());
+            federatedUser.setEnabled(true);
+            federatedUser.setEmail(brokerContext.getEmail());
+            federatedUser.setFirstName(brokerContext.getFirstName());
+            federatedUser.setLastName(brokerContext.getLastName());
+
+            for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet()) {
+                federatedUser.setAttribute(attr.getKey(), attr.getValue());
+            }
+
+            AuthenticatorConfigModel config = context.getAuthenticatorConfig();
+            if (config != null && Boolean.parseBoolean(config.getConfig().get(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION))) {
+                logger.debugf("User '%s' required to update password", federatedUser.getUsername());
+                federatedUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+            }
+
+            // TODO: Event
+
+            context.setUser(federatedUser);
+            context.getClientSession().setNote(BROKER_REGISTERED_NEW_USER, "true");
+            context.success();
+        } else {
+            logger.debugf("Duplication detected. There is already existing user with %s '%s' .",
+                    duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue());
+
+            // Set duplicated user, so next authenticators can deal with it
+            context.getClientSession().setNote(EXISTING_USER_INFO, duplication.serialize());
+
+            Response challengeResponse = context.form()
+                    .setError(Messages.FEDERATED_IDENTITY_EXISTS, duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
+                    .createErrorPage();
+            context.challenge(challengeResponse);
+        }
+    }
+
+    // Could be overriden to detect duplication based on other criterias (firstName, lastName, ...)
+    protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+
+        if (brokerContext.getEmail() != null) {
+            UserModel existingUser = context.getSession().users().getUserByEmail(brokerContext.getEmail(), context.getRealm());
+            if (existingUser != null) {
+                return new ExistingUserInfo(existingUser.getId(), UserModel.EMAIL, existingUser.getEmail());
+            }
+        }
+
+        UserModel existingUser = context.getSession().users().getUserByUsername(brokerContext.getModelUsername(), context.getRealm());
+        if (existingUser != null) {
+            return new ExistingUserInfo(existingUser.getId(), UserModel.USERNAME, existingUser.getUsername());
+        }
+
+        return null;
+    }
+
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java
new file mode 100644
index 0000000..468e8cc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java
@@ -0,0 +1,101 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpCreateUserIfUniqueAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-create-user-if-unique";
+    static IdpCreateUserIfUniqueAuthenticator SINGLETON = new IdpCreateUserIfUniqueAuthenticator();
+
+    public static final String REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION = "require.password.update.after.registration";
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "createUserIfUnique";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return true;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Create User If Unique";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user";
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION);
+        property.setLabel("Require Password Update After Registration");
+        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property.setHelpText("If this option is true and new user is successfully imported from Identity Provider to Keycloak (there is no duplicated email or username detected in Keycloak DB), then this user is required to update his password");
+        configProperties.add(property);
+    }
+
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
new file mode 100644
index 0000000..d6bf10f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -0,0 +1,135 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.requiredactions.VerifyEmail;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailProvider;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.LoginActionsService;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpEmailVerificationAuthenticator.class);
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        KeycloakSession session = context.getSession();
+        RealmModel realm = context.getRealm();
+        ClientSessionModel clientSession = context.getClientSession();
+
+        if (realm.getSmtpConfig().size() == 0) {
+            logger.warnf("Smtp is not configured for the realm. Ignoring email verification authenticator");
+            context.attempted();
+            return;
+        }
+
+        // Create action cookie to detect if email verification happened in same browser
+        LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getClientSession().getId());
+
+        VerifyEmail.setupKey(clientSession);
+
+        UserModel existingUser = getExistingUser(session, realm, clientSession);
+
+        String link = UriBuilder.fromUri(context.getActionUrl())
+                .queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY))
+                .build().toString();
+        long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
+        try {
+
+            context.getSession().getProvider(EmailProvider.class)
+                    .setRealm(realm)
+                    .setUser(existingUser)
+                    .setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
+                    .sendConfirmIdentityBrokerLink(link, expiration);
+//            event.clone().event(EventType.SEND_RESET_PASSWORD)
+//                    .user(user)
+//                    .detail(Details.USERNAME, username)
+//                    .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
+        } catch (EmailException e) {
+//            event.clone().event(EventType.SEND_RESET_PASSWORD)
+//                    .detail(Details.USERNAME, username)
+//                    .user(user)
+//                    .error(Errors.EMAIL_SEND_FAILED);
+            logger.error("Failed to send email to confirm identity broker linking", e);
+            Response challenge = context.form()
+                    .setError(Messages.EMAIL_SENT_ERROR)
+                    .createErrorPage();
+            context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge);
+            return;
+        }
+
+        Response challenge = context.form()
+                .setStatus(Response.Status.OK)
+                .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
+                .createIdpLinkEmailPage();
+        context.forceChallenge(challenge);
+    }
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
+        String key = queryParams.getFirst(Constants.KEY);
+        ClientSessionModel clientSession = context.getClientSession();
+        RealmModel realm = context.getRealm();
+        KeycloakSession session = context.getSession();
+
+        if (key != null) {
+            String keyFromSession = clientSession.getNote(Constants.VERIFY_EMAIL_KEY);
+            clientSession.removeNote(Constants.VERIFY_EMAIL_KEY);
+            if (key.equals(keyFromSession)) {
+                UserModel existingUser = getExistingUser(session, realm, clientSession);
+
+                logger.debugf("User '%s' confirmed that wants to link with identity provider '%s' . Identity provider username is '%s' ", existingUser.getUsername(),
+                        brokerContext.getIdpConfig().getAlias(), brokerContext.getUsername());
+
+                String actionCookieValue = LoginActionsService.getActionCookie(session.getContext().getRequestHeaders(), realm, session.getContext().getUri(), context.getConnection());
+                if (actionCookieValue == null || !actionCookieValue.equals(clientSession.getId())) {
+                    clientSession.setNote(IS_DIFFERENT_BROWSER, "true");
+                }
+
+                context.setUser(existingUser);
+                context.success();
+            } else {
+                logger.error("Key parameter don't match with the expected value from client session");
+                Response challengeResponse = context.form()
+                        .setError(Messages.INVALID_ACCESS_CODE)
+                        .createErrorPage();
+                context.failureChallenge(AuthenticationFlowError.IDENTITY_PROVIDER_ERROR, challengeResponse);
+            }
+        } else {
+            Response challengeResponse = context.form()
+                    .setError(Messages.MISSING_PARAMETER, Constants.KEY)
+                    .createErrorPage();
+            context.failureChallenge(AuthenticationFlowError.IDENTITY_PROVIDER_ERROR, challengeResponse);
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticatorFactory.java
new file mode 100644
index 0000000..e431966
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticatorFactory.java
@@ -0,0 +1,85 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpEmailVerificationAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-email-verification";
+    static IdpEmailVerificationAuthenticator SINGLETON = new IdpEmailVerificationAuthenticator();
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "emailVerification";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Verify existing account by Email";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Email verification of existing Keycloak user, that wants to link his user account with identity provider";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
new file mode 100644
index 0000000..04eb77b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
@@ -0,0 +1,126 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.resources.AttributeFormDataProcessor;
+import org.keycloak.services.validation.Validation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpReviewProfileAuthenticator.class);
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
+        IdentityProviderModel idpConfig = brokerContext.getIdpConfig();
+
+        if (requiresUpdateProfilePage(context, userCtx, brokerContext)) {
+
+            logger.debugf("Identity provider '%s' requires update profile action for broker user '%s'.", idpConfig.getAlias(), userCtx.getUsername());
+
+            // No formData for first render. The profile is rendered from userCtx
+            Response challengeResponse = context.form()
+                    .setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx)
+                    .setFormData(null)
+                    .createUpdateProfilePage();
+            context.challenge(challengeResponse);
+        } else {
+            // Not required to update profile. Marked success
+            context.success();
+        }
+    }
+
+    protected boolean requiresUpdateProfilePage(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
+        String enforceUpdateProfile = context.getClientSession().getNote(ENFORCE_UPDATE_PROFILE);
+        if (Boolean.parseBoolean(enforceUpdateProfile)) {
+            return true;
+        }
+
+        String updateProfileFirstLogin;
+        AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
+        if (authenticatorConfig == null || !authenticatorConfig.getConfig().containsKey(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN)) {
+            updateProfileFirstLogin = IdentityProviderRepresentation.UPFLM_MISSING;
+        } else {
+            updateProfileFirstLogin = authenticatorConfig.getConfig().get(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN);
+        }
+
+        RealmModel realm = context.getRealm();
+        return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin)
+                || (IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin) && !Validation.validateUserMandatoryFields(realm, userCtx));
+    }
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
+        EventBuilder event = context.getEvent();
+        event.event(EventType.UPDATE_PROFILE);
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+
+        RealmModel realm = context.getRealm();
+
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(true, formData);
+        if (errors != null && !errors.isEmpty()) {
+            Response challenge = context.form()
+                    .setErrors(errors)
+                    .setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx)
+                    .setFormData(formData)
+                    .createUpdateProfilePage();
+            context.challenge(challenge);
+            return;
+        }
+
+        userCtx.setUsername(formData.getFirst(UserModel.USERNAME));
+        userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME));
+        userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME));
+
+        String email = formData.getFirst(UserModel.EMAIL);
+        if (!ObjectUtil.isEqualOrBothNull(email, userCtx.getEmail())) {
+            if (logger.isTraceEnabled()) {
+                logger.tracef("Email updated on updateProfile page to '%s' ", email);
+            }
+
+            userCtx.setEmail(email);
+            context.getClientSession().setNote(UPDATE_PROFILE_EMAIL_CHANGED, "true");
+        }
+
+        AttributeFormDataProcessor.process(formData, realm, userCtx);
+
+        userCtx.saveToClientSession(context.getClientSession());
+
+        logger.debugf("Profile updated successfully after first authentication with identity provider '%s' for broker user '%s'.", brokerContext.getIdpConfig().getAlias(), userCtx.getUsername());
+
+        event.detail(Details.UPDATED_EMAIL, email);
+        context.success();
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java
new file mode 100644
index 0000000..e10c924
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java
@@ -0,0 +1,107 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpReviewProfileAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-review-profile";
+    static IdpReviewProfileAuthenticator SINGLETON = new IdpReviewProfileAuthenticator();
+
+    public static final String UPDATE_PROFILE_ON_FIRST_LOGIN = "update.profile.on.first.login";
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "reviewProfile";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return true;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Review Profile";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "User reviews and updates profile data retrieved from Identity Provider in the displayed form";
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(UPDATE_PROFILE_ON_FIRST_LOGIN);
+        property.setLabel("{{:: 'update-profile-on-first-login' | translate}}");
+        property.setType(ProviderConfigProperty.LIST_TYPE);
+        List<String> updateProfileValues = Arrays.asList(IdentityProviderRepresentation.UPFLM_ON, IdentityProviderRepresentation.UPFLM_MISSING, IdentityProviderRepresentation.UPFLM_OFF);
+        property.setDefaultValue(updateProfileValues);
+        property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that"
+                + " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed."
+                + " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one");
+
+        configProperties.add(property);
+    }
+
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
new file mode 100644
index 0000000..b293d1b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
@@ -0,0 +1,55 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * Same like classic username+password form, but username is "known" and user can't change it
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpUsernamePasswordForm extends UsernamePasswordForm {
+
+    @Override
+    protected Response challenge(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
+        UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
+
+        return setupForm(context, formData, existingUser)
+                .setStatus(Response.Status.OK)
+                .createLogin();
+    }
+
+    @Override
+    protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
+        UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
+        context.setUser(existingUser);
+
+        // Restore formData for the case of error
+        setupForm(context, formData, existingUser);
+
+        return validatePassword(context, formData);
+    }
+
+    protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData, UserModel existingUser) {
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(context.getClientSession());
+        if (serializedCtx == null) {
+            throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+
+        formData.add(AuthenticationManager.FORM_USERNAME, existingUser.getUsername());
+        return context.form()
+                .setFormData(formData)
+                .setAttribute(LoginFormsProvider.USERNAME_EDIT_DISABLED, true)
+                .setSuccess(Messages.FEDERATED_IDENTITY_CONFIRM_REAUTHENTICATE_MESSAGE, existingUser.getUsername(), serializedCtx.getIdentityProviderId());
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordFormFactory.java
new file mode 100644
index 0000000..2adaead
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordFormFactory.java
@@ -0,0 +1,35 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpUsernamePasswordFormFactory extends UsernamePasswordFormFactory {
+
+    public static final String PROVIDER_ID = "idp-username-password-form";
+    public static final UsernamePasswordForm IDP_SINGLETON = new IdpUsernamePasswordForm();
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return IDP_SINGLETON;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Validates a password from login form. Username is already known from identity provider authentication";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Username Password Form for identity provider reauthentication";
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/ExistingUserInfo.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/ExistingUserInfo.java
new file mode 100644
index 0000000..2d6ea7f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/ExistingUserInfo.java
@@ -0,0 +1,62 @@
+package org.keycloak.authentication.authenticators.broker.util;
+
+import java.io.IOException;
+
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExistingUserInfo {
+    private String existingUserId;
+    private String duplicateAttributeName;
+    private String duplicateAttributeValue;
+
+    public ExistingUserInfo() {}
+
+    public ExistingUserInfo(String existingUserId, String duplicateAttributeName, String duplicateAttributeValue) {
+        this.existingUserId = existingUserId;
+        this.duplicateAttributeName = duplicateAttributeName;
+        this.duplicateAttributeValue = duplicateAttributeValue;
+    }
+
+    public String getExistingUserId() {
+        return existingUserId;
+    }
+
+    public void setExistingUserId(String existingUserId) {
+        this.existingUserId = existingUserId;
+    }
+
+    public String getDuplicateAttributeName() {
+        return duplicateAttributeName;
+    }
+
+    public void setDuplicateAttributeName(String duplicateAttributeName) {
+        this.duplicateAttributeName = duplicateAttributeName;
+    }
+
+    public String getDuplicateAttributeValue() {
+        return duplicateAttributeValue;
+    }
+
+    public void setDuplicateAttributeValue(String duplicateAttributeValue) {
+        this.duplicateAttributeValue = duplicateAttributeValue;
+    }
+
+    public String serialize() {
+        try {
+            return JsonSerialization.writeValueAsString(this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static ExistingUserInfo deserialize(String serialized) {
+        try {
+            return JsonSerialization.readValue(serialized, ExistingUserInfo.class);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
new file mode 100644
index 0000000..8f1b026
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
@@ -0,0 +1,327 @@
+package org.keycloak.authentication.authenticators.broker.util;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.reflections.Reflections;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.IdentityBrokerService;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
+
+    private String id;
+    private String brokerUsername;
+    private String modelUsername;
+    private String email;
+    private String firstName;
+    private String lastName;
+    private String brokerSessionId;
+    private String brokerUserId;
+    private String code;
+    private String token;
+
+    private String identityProviderId;
+    private Map<String, ContextDataEntry> contextData = new HashMap<>();
+
+    @JsonIgnore
+    @Override
+    public boolean isEditUsernameAllowed() {
+        return true;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getUsername() {
+        return modelUsername;
+    }
+
+    @Override
+    public void setUsername(String username) {
+        this.modelUsername = username;
+    }
+
+    public String getModelUsername() {
+        return modelUsername;
+    }
+
+    public void setModelUsername(String modelUsername) {
+        this.modelUsername = modelUsername;
+    }
+
+    public String getBrokerUsername() {
+        return brokerUsername;
+    }
+
+    public void setBrokerUsername(String modelUsername) {
+        this.brokerUsername = modelUsername;
+    }
+
+    @Override
+    public String getEmail() {
+        return email;
+    }
+
+    @Override
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    @Override
+    public String getFirstName() {
+        return firstName;
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    @Override
+    public String getLastName() {
+        return lastName;
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public String getBrokerSessionId() {
+        return brokerSessionId;
+    }
+
+    public void setBrokerSessionId(String brokerSessionId) {
+        this.brokerSessionId = brokerSessionId;
+    }
+
+    public String getBrokerUserId() {
+        return brokerUserId;
+    }
+
+    public void setBrokerUserId(String brokerUserId) {
+        this.brokerUserId = brokerUserId;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getIdentityProviderId() {
+        return identityProviderId;
+    }
+
+    public void setIdentityProviderId(String identityProviderId) {
+        this.identityProviderId = identityProviderId;
+    }
+
+    public Map<String, ContextDataEntry> getContextData() {
+        return contextData;
+    }
+
+    public void setContextData(Map<String, ContextDataEntry> contextData) {
+        this.contextData = contextData;
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        Map<String, List<String>> result = new HashMap<>();
+
+        for (Map.Entry<String, ContextDataEntry> entry : this.contextData.entrySet()) {
+            if (entry.getKey().startsWith("user.attributes.")) {
+                ContextDataEntry ctxEntry = entry.getValue();
+                String asString = ctxEntry.getData();
+                try {
+                    List<String> asList = JsonSerialization.readValue(asString, List.class);
+                    result.put(entry.getKey().substring(16), asList);
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setAttribute(String key, List<String> value) {
+        try {
+            String listStr = JsonSerialization.writeValueAsString(value);
+            ContextDataEntry ctxEntry = ContextDataEntry.create(List.class.getName(), listStr);
+            this.contextData.put("user.attributes." + key, ctxEntry);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    @Override
+    public List<String> getAttribute(String key) {
+        ContextDataEntry ctxEntry = this.contextData.get("user.attributes." + key);
+        if (ctxEntry != null) {
+            try {
+                String asString = ctxEntry.getData();
+                List<String> asList = JsonSerialization.readValue(asString, List.class);
+                return asList;
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public BrokeredIdentityContext deserialize(KeycloakSession session, ClientSessionModel clientSession) {
+        BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId());
+
+        ctx.setUsername(getBrokerUsername());
+        ctx.setModelUsername(getModelUsername());
+        ctx.setEmail(getEmail());
+        ctx.setFirstName(getFirstName());
+        ctx.setLastName(getLastName());
+        ctx.setBrokerSessionId(getBrokerSessionId());
+        ctx.setBrokerUserId(getBrokerUserId());
+        ctx.setCode(getCode());
+        ctx.setToken(getToken());
+
+        RealmModel realm = clientSession.getRealm();
+        IdentityProviderModel idpConfig = realm.getIdentityProviderByAlias(getIdentityProviderId());
+        if (idpConfig == null) {
+            throw new ModelException("Can't find identity provider with ID " + getIdentityProviderId() + " in realm " + realm.getName());
+        }
+        IdentityProvider idp = IdentityBrokerService.getIdentityProvider(session, realm, idpConfig.getAlias());
+        ctx.setIdpConfig(idpConfig);
+        ctx.setIdp(idp);
+
+        IdentityProviderDataMarshaller serializer = idp.getMarshaller();
+
+        for (Map.Entry<String, ContextDataEntry> entry : getContextData().entrySet()) {
+            try {
+                ContextDataEntry value = entry.getValue();
+                Class<?> clazz = Reflections.classForName(value.getClazz(), this.getClass().getClassLoader());
+
+                Object deserialized = serializer.deserialize(value.getData(), clazz);
+
+                ctx.getContextData().put(entry.getKey(), deserialized);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        ctx.setClientSession(clientSession);
+        return ctx;
+    }
+
+    public static SerializedBrokeredIdentityContext serialize(BrokeredIdentityContext context) {
+        SerializedBrokeredIdentityContext ctx = new SerializedBrokeredIdentityContext();
+        ctx.setId(context.getId());
+        ctx.setBrokerUsername(context.getUsername());
+        ctx.setModelUsername(context.getModelUsername());
+        ctx.setEmail(context.getEmail());
+        ctx.setFirstName(context.getFirstName());
+        ctx.setLastName(context.getLastName());
+        ctx.setBrokerSessionId(context.getBrokerSessionId());
+        ctx.setBrokerUserId(context.getBrokerUserId());
+        ctx.setCode(context.getCode());
+        ctx.setToken(context.getToken());
+        ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
+
+        IdentityProviderDataMarshaller serializer = context.getIdp().getMarshaller();
+
+        for (Map.Entry<String, Object> entry : context.getContextData().entrySet()) {
+            Object value = entry.getValue();
+            String serializedValue = serializer.serialize(value);
+
+            ContextDataEntry ctxEntry = ContextDataEntry.create(value.getClass().getName(), serializedValue);
+            ctx.getContextData().put(entry.getKey(), ctxEntry);
+        }
+        return ctx;
+    }
+
+    // Save this context as note to clientSession
+    public void saveToClientSession(ClientSessionModel clientSession) {
+        try {
+            String asString = JsonSerialization.writeValueAsString(this);
+            clientSession.setNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE, asString);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    public static SerializedBrokeredIdentityContext readFromClientSession(ClientSessionModel clientSession) {
+        String asString = clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+        if (asString == null) {
+            return null;
+        } else {
+            try {
+                return JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+
+    public static class ContextDataEntry {
+
+        private String clazz;
+        private String data;
+
+        public String getClazz() {
+            return clazz;
+        }
+
+        public void setClazz(String clazz) {
+            this.clazz = clazz;
+        }
+
+        public String getData() {
+            return data;
+        }
+
+        public void setData(String data) {
+            this.data = data;
+        }
+
+        public static ContextDataEntry create(String clazz, String data) {
+            ContextDataEntry entry = new ContextDataEntry();
+            entry.setClazz(clazz);
+            entry.setData(data);
+            return entry;
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
index a624495..a3538aa 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
@@ -1,10 +1,12 @@
 package org.keycloak.authentication.authenticators.resetcred;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
 import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
@@ -36,10 +38,22 @@ import java.util.concurrent.TimeUnit;
  */
 public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFactory {
 
+    protected static Logger logger = Logger.getLogger(ResetCredentialChooseUser.class);
+
     public static final String PROVIDER_ID = "reset-credentials-choose-user";
 
     @Override
     public void authenticate(AuthenticationFlowContext context) {
+        String existingUserId = context.getClientSession().getNote(AbstractIdpAuthenticator.EXISTING_USER_INFO);
+        if (existingUserId != null) {
+            UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
+
+            logger.debugf("Forget-password triggered when reauthenticating user after first broker login. Skipping reset-credential-choose-user screen and using user '%s' ", existingUser.getUsername());
+            context.setUser(existingUser);
+            context.success();
+            return;
+        }
+
         Response challenge = context.form().createPasswordReset();
         context.challenge(challenge);
     }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
index 8749360..678aac2 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
@@ -14,6 +14,7 @@ import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
@@ -34,7 +35,7 @@ import java.util.concurrent.TimeUnit;
  */
 public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory {
     public static final String RESET_CREDENTIAL_SECRET = "RESET_CREDENTIAL_SECRET";
-    public static final String KEY = "key";
+
     protected static Logger logger = Logger.getLogger(ResetCredentialEmail.class);
 
     public static final String PROVIDER_ID = "reset-credential-email";
@@ -67,7 +68,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
         // it can only be guessed once, and it must match watch is stored in the client session.
         String secret = HmacOTP.generateSecret(10);
         context.getClientSession().setNote(RESET_CREDENTIAL_SECRET, secret);
-        String link = UriBuilder.fromUri(context.getActionUrl()).queryParam(KEY, secret).build().toString();
+        String link = UriBuilder.fromUri(context.getActionUrl()).queryParam(Constants.KEY, secret).build().toString();
         long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
         try {
 
@@ -93,7 +94,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
     @Override
     public void action(AuthenticationFlowContext context) {
         String secret = context.getClientSession().getNote(RESET_CREDENTIAL_SECRET);
-        String key = context.getUriInfo().getQueryParameters().getFirst(KEY);
+        String key = context.getUriInfo().getQueryParameters().getFirst(Constants.KEY);
 
         // Can only guess once!  We remove the note so another guess can't happen
         context.getClientSession().removeNote(RESET_CREDENTIAL_SECRET);
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
index 9630f3b..107f7cc 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
@@ -52,7 +52,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
         RealmModel realm = context.getRealm();
 
 
-        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm, formData);
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm.isEditUsernameAllowed(), formData);
         if (errors != null && !errors.isEmpty()) {
             Response challenge = context.form()
                     .setErrors(errors)
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java
new file mode 100644
index 0000000..2acef37
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java
@@ -0,0 +1,38 @@
+package org.keycloak.authentication.requiredactions.util;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstraction, which allows to display updateProfile page in various contexts (Required action of already existing user, or first identity provider
+ * login when user doesn't yet exists in Keycloak DB)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UpdateProfileContext {
+
+    boolean isEditUsernameAllowed();
+
+    String getUsername();
+
+    void setUsername(String username);
+
+    String getEmail();
+
+    void setEmail(String email);
+
+    String getFirstName();
+
+    void setFirstName(String firstName);
+
+    String getLastName();
+
+    void setLastName(String lastName);
+
+    Map<String, List<String>> getAttributes();
+
+    void setAttribute(String key, List<String> value);
+
+    List<String> getAttribute(String key);
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java
new file mode 100644
index 0000000..55d6dda
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java
@@ -0,0 +1,81 @@
+package org.keycloak.authentication.requiredactions.util;
+
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserUpdateProfileContext implements UpdateProfileContext {
+
+    private final RealmModel realm;
+    private final UserModel user;
+
+    public UserUpdateProfileContext(RealmModel realm, UserModel user) {
+        this.realm = realm;
+        this.user = user;
+    }
+
+    @Override
+    public boolean isEditUsernameAllowed() {
+        return realm.isEditUsernameAllowed();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUsername();
+    }
+
+    @Override
+    public void setUsername(String username) {
+        user.setUsername(username);
+    }
+
+    @Override
+    public String getEmail() {
+        return user.getEmail();
+    }
+
+    @Override
+    public void setEmail(String email) {
+        user.setEmail(email);
+    }
+
+    @Override
+    public String getFirstName() {
+        return user.getFirstName();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        user.setFirstName(firstName);
+    }
+
+    @Override
+    public String getLastName() {
+        return user.getLastName();
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        user.setLastName(lastName);
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return user.getAttributes();
+    }
+
+    @Override
+    public void setAttribute(String key, List<String> value) {
+        user.setAttribute(key, value);
+    }
+
+    @Override
+    public List<String> getAttribute(String key) {
+        return user.getAttribute(key);
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index d092d16..5f2eeb3 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -64,9 +64,13 @@ public class Messages {
 
     public static final String EMAIL_EXISTS = "emailExistsMessage";
 
-    public static final String FEDERATED_IDENTITY_EMAIL_EXISTS = "federatedIdentityEmailExistsMessage";
+    public static final String FEDERATED_IDENTITY_EXISTS = "federatedIdentityExistsMessage";
 
-    public static final String FEDERATED_IDENTITY_USERNAME_EXISTS = "federatedIdentityUsernameExistsMessage";
+    public static final String FEDERATED_IDENTITY_CONFIRM_LINK_MESSAGE = "federatedIdentityConfirmLinkMessage";
+
+    public static final String FEDERATED_IDENTITY_CONFIRM_REAUTHENTICATE_MESSAGE = "federatedIdentityConfirmReauthenticateMessage";
+
+    public static final String IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE = "identityProviderDifferentUserMessage";
 
     public static final String CONFIGURE_TOTP = "configureTotpMessage";
 
@@ -76,6 +80,8 @@ public class Messages {
 
     public static final String VERIFY_EMAIL = "verifyEmailMessage";
 
+    public static final String LINK_IDP = "linkIdpMessage";
+
     public static final String EMAIL_VERIFIED = "emailVerifiedMessage";
 
     public static final String EMAIL_SENT = "emailSentMessage";
@@ -147,6 +153,8 @@ public class Messages {
 
     public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFoundMessage";
 
+    public static final String IDENTITY_PROVIDER_LINK_SUCCESS = "identityProviderLinkSuccess";
+
     public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage";
 
     public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index dc5fa8d..71f849b 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -366,7 +366,7 @@ public class AccountService extends AbstractSecuredLocalService {
 
         UserModel user = auth.getUser();
 
-        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm, formData);
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm.isEditUsernameAllowed(), formData);
         if (errors != null && !errors.isEmpty()) {
             setReferrerOnPage();
             return account.setErrors(errors).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 063e66c..9ff6807 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -276,6 +276,10 @@ public class AuthenticationManagementResource {
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createFlow(AuthenticationFlowModel model) {
         this.auth.requireManage();
+        
+        if (model.getAlias() == null || model.getAlias().isEmpty()) {
+            return ErrorResponse.exists("Failed to create flow with empty alias name");
+        }
 
         if (realm.getFlowByAlias(model.getAlias()) != null) {
             return ErrorResponse.exists("Flow " + model.getAlias() + " already exists");
@@ -711,6 +715,12 @@ public class AuthenticationManagementResource {
         if (parentFlow.isBuiltIn()) {
             throw new BadRequestException("It is illegal to remove execution from a built in flow");
         }
+        
+        if(model.getFlowId() != null) {
+        	AuthenticationFlowModel nonTopLevelFlow = realm.getAuthenticationFlowById(model.getFlowId());
+        	realm.removeAuthenticationFlow(nonTopLevelFlow);
+        }
+		
         realm.removeAuthenticatorExecution(model);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index bf17f4b..b49cf91 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -79,7 +79,7 @@ public class IdentityProviderResource {
     @Produces(MediaType.APPLICATION_JSON)
     public IdentityProviderRepresentation getIdentityProvider() {
         this.auth.requireView();
-        IdentityProviderRepresentation rep = ModelToRepresentation.toRepresentation(this.identityProviderModel);
+        IdentityProviderRepresentation rep = ModelToRepresentation.toRepresentation(realm, this.identityProviderModel);
         return rep;
     }
 
@@ -117,7 +117,7 @@ public class IdentityProviderResource {
             String newProviderId = providerRep.getAlias();
             String oldProviderId = getProviderIdByInternalId(this.realm, internalId);
 
-            this.realm.updateIdentityProvider(RepresentationToModel.toModel(providerRep));
+            this.realm.updateIdentityProvider(RepresentationToModel.toModel(realm, providerRep));
 
             if (oldProviderId != null && !oldProviderId.equals(newProviderId)) {
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index d3dc33a..bf452ff 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -145,7 +145,7 @@ public class IdentityProvidersResource {
         List<IdentityProviderRepresentation> representations = new ArrayList<IdentityProviderRepresentation>();
 
         for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
-            representations.add(ModelToRepresentation.toRepresentation(identityProviderModel));
+            representations.add(ModelToRepresentation.toRepresentation(realm, identityProviderModel));
         }
         return representations;
     }
@@ -164,7 +164,7 @@ public class IdentityProvidersResource {
         this.auth.requireManage();
 
         try {
-            IdentityProviderModel identityProvider = RepresentationToModel.toModel(representation);
+            IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation);
             this.realm.addIdentityProvider(identityProvider);
 
             adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, identityProvider.getInternalId())
diff --git a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
index c9ec4c3..9fb60ec 100755
--- a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
+++ b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
@@ -3,6 +3,8 @@ package org.keycloak.services.resources;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
@@ -21,6 +23,11 @@ public class AttributeFormDataProcessor {
      * @param user
      */
     public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UserModel user) {
+        UpdateProfileContext userCtx = new UserUpdateProfileContext(realm, user);
+        process(formData, realm, userCtx);
+    }
+
+    public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UpdateProfileContext user) {
         for (String key : formData.keySet()) {
             if (!key.startsWith("user.attributes.")) continue;
             String attribute = key.substring("user.attributes.".length());
@@ -36,7 +43,6 @@ public class AttributeFormDataProcessor {
 
             user.setAttribute(attribute, modelValue);
         }
-
     }
 
     private static void addOrSetValue(List<String> list, int index, String value) {
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 a171bdb..fda37ef 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -20,6 +20,9 @@ package org.keycloak.services.resources;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.broker.provider.AuthenticationRequest;
@@ -28,10 +31,12 @@ import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
@@ -49,7 +54,6 @@ import org.keycloak.models.utils.FormMessage;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.managers.AppAuthManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthResult;
 import org.keycloak.services.managers.BruteForceProtector;
@@ -70,6 +74,7 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -79,7 +84,6 @@ import java.util.Set;
 import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
 import static org.keycloak.models.ClientSessionModel.Action.AUTHENTICATE;
 import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
-import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
 
 /**
  * <p></p>
@@ -285,27 +289,133 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         if (federatedUser == null) {
-            try {
-                federatedUser = createUser(context);
 
-                if (IdentityProviderRepresentation.UPFLM_ON.equals(identityProviderConfig.getUpdateProfileFirstLoginMode()) 
-                        || (IdentityProviderRepresentation.UPFLM_MISSING.equals(identityProviderConfig.getUpdateProfileFirstLoginMode()) && !Validation.validateUserMandatoryFields(realmModel, federatedUser))) {
-                    if (isDebugEnabled()) {
-                        LOGGER.debugf("Identity provider requires update profile action.", federatedUser);
-                    }
-                    federatedUser.addRequiredAction(UPDATE_PROFILE);
-                }
-                if(identityProviderConfig.isTrustEmail() && !Validation.isBlank(federatedUser.getEmail())){
-                    federatedUser.setEmailVerified(true);
+            LOGGER.debugf("Federated user not found for provider '%s' and broker username '%s' . Redirecting to flow for firstBrokerLogin", providerId, context.getUsername());
+
+            String username = context.getModelUsername();
+            if (username == null) {
+                if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isBlank(context.getEmail())) {
+                    username = context.getEmail();
+                } else if (context.getUsername() == null) {
+                    username = context.getIdpConfig().getAlias() + "." + context.getId();
+                } else {
+                    username = context.getIdpConfig().getAlias() + "." + context.getUsername();
                 }
-            } catch (Exception e) {
-                return redirectToLoginPage(e, clientCode);
             }
+            username = username.trim();
+            context.setModelUsername(username);
+
+            clientSession.setTimestamp(Time.currentTime());
+
+            SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
+            ctx.saveToClientSession(clientSession);
+
+            URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
+                    .queryParam(OAuth2Constants.CODE, context.getCode())
+                    .build(realmModel.getName());
+            return Response.status(302).location(redirect).build();
+
         } else {
             updateFederatedIdentity(context, federatedUser);
+
+            boolean firstBrokerLoginInProgress = (clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+            if (firstBrokerLoginInProgress) {
+                LOGGER.debugf("Reauthenticated with broker '%s' when linking user '%s' with other broker", context.getIdpConfig().getAlias(), federatedUser.getUsername());
+
+                UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realmModel, clientSession);
+                if (!linkingUser.getId().equals(federatedUser.getId())) {
+                    return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
+                }
+
+                clientSession.setAuthenticatedUser(federatedUser);
+                return afterFirstBrokerLogin(context.getCode());
+            }
+
+            return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
         }
+    }
+
+    // Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created
+    @GET
+    @Path("/after-first-broker-login")
+    public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
+        ClientSessionCode clientCode = parseClientSessionCode(code);
+        ClientSessionModel clientSession = clientCode.getClientSession();
+
+        try {
+            SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+            if (serializedCtx == null) {
+                throw new IdentityBrokerException("Not found serialized context in clientSession");
+            }
+            BrokeredIdentityContext context = serializedCtx.deserialize(session, clientSession);
+            String providerId = context.getIdpConfig().getAlias();
 
+            // firstBrokerLogin workflow finished. Removing note now
+            clientSession.removeNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+
+            UserModel federatedUser = clientSession.getAuthenticatedUser();
+            if (federatedUser == null) {
+                throw new IdentityBrokerException("Couldn't found authenticated federatedUser in clientSession");
+            }
+
+            if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
+                RoleModel readTokenRole = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+                federatedUser.grantRole(readTokenRole);
+            }
+
+            // Add federated identity link here
+            FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
+                    context.getUsername(), context.getToken());
+            session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel);
+
+            String isRegisteredNewUser = clientSession.getNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER);
+            if (Boolean.parseBoolean(isRegisteredNewUser)) {
+
+                LOGGER.debugf("Registered new user '%s' after first login with identity provider '%s'. Identity provider username is '%s' . ", federatedUser.getUsername(), providerId, context.getUsername());
+
+                context.getIdp().importNewUser(session, realmModel, federatedUser, context);
+                Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(providerId);
+                if (mappers != null) {
+                    KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+                    for (IdentityProviderMapperModel mapper : mappers) {
+                        IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
+                        target.importNewUser(session, realmModel, federatedUser, mapper, context);
+                    }
+                }
+
+                if (context.getIdpConfig().isTrustEmail() && !Validation.isBlank(federatedUser.getEmail()) && !Boolean.parseBoolean(clientSession.getNote(AbstractIdpAuthenticator.UPDATE_PROFILE_EMAIL_CHANGED))) {
+                    LOGGER.debugf("Email verified automatically after registration of user '%s' through Identity provider '%s' ", federatedUser.getUsername(), context.getIdpConfig().getAlias());
+                    federatedUser.setEmailVerified(true);
+                }
+
+                this.event.clone().user(federatedUser).event(EventType.REGISTER)
+                        .detail(Details.IDENTITY_PROVIDER, providerId)
+                        .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
+                        .removeDetail("auth_method")
+                        .success();
+
+            } else {
+                LOGGER.debugf("Linked existing keycloak user '%s' with identity provider '%s' . Identity provider username is '%s' .", federatedUser.getUsername(), providerId, context.getUsername());
+
+                updateFederatedIdentity(context, federatedUser);
+            }
+
+            String isDifferentBrowser = clientSession.getNote(AbstractIdpAuthenticator.IS_DIFFERENT_BROWSER);
+            if (Boolean.parseBoolean(isDifferentBrowser)) {
+                session.sessions().removeClientSession(realmModel, clientSession);
+                return session.getProvider(LoginFormsProvider.class)
+                            .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, context.getIdpConfig().getAlias(), context.getUsername())
+                            .createInfoPage();
+            } else {
+                return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
+            }
+        }  catch (Exception e) {
+            // TODO?
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
+        }
+    }
 
+    private Response finishBrokerAuthentication(BrokeredIdentityContext context, UserModel federatedUser, ClientSessionModel clientSession, String providerId) {
         UserSessionModel userSession = this.session.sessions()
                 .createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false, context.getBrokerSessionId(), context.getBrokerUserId());
 
@@ -376,7 +486,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
 
         // Skip DB write if tokens are null or equal
-        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrNull(context.getToken(), federatedIdentityModel.getToken())) {
+        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
             federatedIdentityModel.setToken(context.getToken());
 
             this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
@@ -412,6 +522,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
                 LOGGER.debugf("Got authorization code from client [%s].", client.getClientId());
                 this.event.client(client);
+                this.session.getContext().setClient(client);
 
                 if (clientSession.getUserSession() != null) {
                     this.event.session(clientSession.getUserSession());
@@ -534,100 +645,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     }
 
     private IdentityProviderModel getIdentityProviderConfig(String providerId) {
-        for (IdentityProviderModel model : this.realmModel.getIdentityProviders()) {
-            if (model.getAlias().equals(providerId)) {
-                return model;
-            }
-        }
-
-        throw new IdentityBrokerException("Configuration for identity provider [" + providerId + "] not found.");
-    }
-
-    private UserModel createUser(BrokeredIdentityContext context) {
-        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
-                context.getUsername(), context.getToken());
-        // Check if no user already exists with this username or email
-        UserModel existingUser = null;
-
-        if (context.getEmail() != null) {
-            existingUser = this.session.users().getUserByEmail(context.getEmail(), this.realmModel);
+        IdentityProviderModel model = this.realmModel.getIdentityProviderByAlias(providerId);
+        if (model == null) {
+            throw new IdentityBrokerException("Configuration for identity provider [" + providerId + "] not found.");
         }
-
-        if (existingUser != null) {
-            fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS);
-            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS);
-        }
-        String username = context.getModelUsername();
-        if (username == null) {
-            username = context.getUsername();
-            if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isBlank(context.getEmail())) {
-                username = context.getEmail();
-            } else if (username == null) {
-                username = context.getIdpConfig().getAlias() + "." + context.getId();
-            } else {
-                username = context.getIdpConfig().getAlias() + "." + context.getUsername();
-            }
-        }
-        if (username == null) {
-            LOGGER.warn("Unknown username");
-            fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
-            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS);
-
-        }
-        username = username.trim();
-
-        existingUser = this.session.users().getUserByUsername(username, this.realmModel);
-
-        if (existingUser != null) {
-            fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
-            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS);
-        }
-
-        if (isDebugEnabled()) {
-            LOGGER.debugf("Creating account from identity [%s].", federatedIdentityModel);
-        }
-
-        UserModel federatedUser = this.session.users().addUser(this.realmModel, username);
-
-        if (isDebugEnabled()) {
-            LOGGER.debugf("Account [%s] created.", federatedUser);
-        }
-
-        federatedUser.setEnabled(true);
-        federatedUser.setEmail(context.getEmail());
-        federatedUser.setFirstName(context.getFirstName());
-        federatedUser.setLastName(context.getLastName());
-
-
-        if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
-            RoleModel readTokenRole = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
-            federatedUser.grantRole(readTokenRole);
-        }
-
-        if (context.getIdpConfig().isStoreToken()) {
-            federatedIdentityModel.setToken(context.getToken());
-        }
-
-        this.session.users().addFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
-
-        context.getIdp().importNewUser(session, realmModel, federatedUser, context);
-        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
-        if (mappers != null) {
-            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-            for (IdentityProviderMapperModel mapper : mappers) {
-                IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-                target.importNewUser(session, realmModel, federatedUser, mapper, context);
-            }
-        }
-
-
-        this.event.clone().user(federatedUser).event(EventType.REGISTER)
-                .detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider())
-                .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
-                .removeDetail("auth_method")
-                .success();
-
-        return federatedUser;
+        return model;
     }
 
     private Response corsResponse(Response response, ClientModel clientModel) {
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 20de78b..8f845c0 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -23,8 +23,10 @@ package org.keycloak.services.resources;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
 import org.keycloak.authentication.requiredactions.VerifyEmail;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationProcessor;
@@ -33,6 +35,7 @@ import org.keycloak.authentication.RequiredActionContextResult;
 import org.keycloak.authentication.RequiredActionFactory;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
+import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -51,7 +54,6 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.FormMessage;
-import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.RestartLoginCookie;
@@ -92,6 +94,7 @@ public class LoginActionsService {
     public static final String REGISTRATION_PATH = "registration";
     public static final String RESET_CREDENTIALS_PATH = "reset-credentials";
     public static final String REQUIRED_ACTION = "required-action";
+    public static final String FIRST_BROKER_LOGIN_PATH = "first-broker-login";
 
     private RealmModel realm;
 
@@ -134,6 +137,10 @@ public class LoginActionsService {
         return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "processRegister");
     }
 
+    public static UriBuilder firstBrokerLoginProcessor(UriInfo uriInfo) {
+        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "firstBrokerLoginGet");
+    }
+
     public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
         return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
     }
@@ -208,7 +215,7 @@ public class LoginActionsService {
                         ClientSessionModel clientSession = RestartLoginCookie.restartSession(session, realm, code);
                         if (clientSession != null) {
                             event.clone().detail(Details.RESTART_AFTER_TIMEOUT, "true").error(Errors.EXPIRED_CODE);
-                            response = processFlow(null, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), Messages.LOGIN_TIMEOUT);
+                            response = processFlow(null, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), Messages.LOGIN_TIMEOUT, new AuthenticationProcessor());
                             return false;
                         }
                     } catch (Exception e) {
@@ -267,11 +274,10 @@ public class LoginActionsService {
     }
 
     protected Response processAuthentication(String execution, ClientSessionModel clientSession, String errorMessage) {
-        return processFlow(execution, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage);
+        return processFlow(execution, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage, new AuthenticationProcessor());
     }
 
-    protected Response processFlow(String execution, ClientSessionModel clientSession, String flowPath, AuthenticationFlowModel flow, String errorMessage) {
-        AuthenticationProcessor processor = new AuthenticationProcessor();
+    protected Response processFlow(String execution, ClientSessionModel clientSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
         processor.setClientSession(clientSession)
                 .setFlowPath(flowPath)
                 .setBrowserFlow(true)
@@ -384,12 +390,33 @@ public class LoginActionsService {
     }
 
     protected Response processResetCredentials(String execution, ClientSessionModel clientSession, String errorMessage) {
-        return processFlow(execution, clientSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), errorMessage);
+        AuthenticationProcessor authProcessor = new AuthenticationProcessor() {
+
+            @Override
+            protected Response authenticationComplete() {
+                boolean firstBrokerLoginInProgress = (clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+                if (firstBrokerLoginInProgress) {
+
+                    UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, clientSession);
+                    if (!linkingUser.getId().equals(clientSession.getAuthenticatedUser().getId())) {
+                        return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, clientSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
+                    }
+
+                    logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login.", linkingUser.getUsername());
+
+                    return redirectToAfterFirstBrokerLoginEndpoint(clientSession);
+                } else {
+                    return super.authenticationComplete();
+                }
+            }
+        };
+
+        return processFlow(execution, clientSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), errorMessage, authProcessor);
     }
 
 
     protected Response processRegistration(String execution, ClientSessionModel clientSession, String errorMessage) {
-        return processFlow(execution, clientSession, REGISTRATION_PATH, realm.getRegistrationFlow(), errorMessage);
+        return processFlow(execution, clientSession, REGISTRATION_PATH, realm.getRegistrationFlow(), errorMessage, new AuthenticationProcessor());
     }
 
 
@@ -450,6 +477,60 @@ public class LoginActionsService {
         return processRegistration(execution, clientSession, null);
     }
 
+    @Path(FIRST_BROKER_LOGIN_PATH)
+    @GET
+    public Response firstBrokerLoginGet(@QueryParam("code") String code,
+                                 @QueryParam("execution") String execution) {
+        return firstBrokerLogin(code, execution);
+    }
+
+    @Path(FIRST_BROKER_LOGIN_PATH)
+    @POST
+    public Response firstBrokerLoginPost(@QueryParam("code") String code,
+                                        @QueryParam("execution") String execution) {
+        return firstBrokerLogin(code, execution);
+    }
+
+    protected Response firstBrokerLogin(String code, String execution) {
+        event.event(EventType.IDENTITY_PROVIDER_FIRST_LOGIN);
+
+        Checks checks = new Checks();
+        if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+            return checks.response;
+        }
+        event.detail(Details.CODE_ID, code);
+        ClientSessionCode clientSessionCode = checks.clientCode;
+        ClientSessionModel clientSession = clientSessionCode.getClientSession();
+
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+        if (serializedCtx == null) {
+            throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in clientSession"));
+        }
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, clientSession);
+        AuthenticationFlowModel firstBrokerLoginFlow = realm.getAuthenticationFlowById(brokerContext.getIdpConfig().getFirstBrokerLoginFlowId());
+
+        AuthenticationProcessor processor = new AuthenticationProcessor() {
+
+            @Override
+            protected Response authenticationComplete() {
+                return redirectToAfterFirstBrokerLoginEndpoint(clientSession);
+            }
+
+        };
+
+        return processFlow(execution, clientSession, FIRST_BROKER_LOGIN_PATH, firstBrokerLoginFlow, null, processor);
+    }
+
+    private Response redirectToAfterFirstBrokerLoginEndpoint(ClientSessionModel clientSession) {
+        ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+        clientSession.setTimestamp(Time.currentTime());
+
+        URI redirect = Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode());
+        logger.debugf("Redirecting to '%s' ", redirect);
+
+        return Response.status(302).location(redirect).build();
+    }
+
     /**
      * OAuth grant page.  You should not invoked this directly!
      *
@@ -627,6 +708,10 @@ public class LoginActionsService {
     }
 
     private String getActionCookie() {
+        return getActionCookie(headers, realm, uriInfo, clientConnection);
+    }
+
+    public static String getActionCookie(HttpHeaders headers, RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection) {
         Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
         AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
         return cookie != null ? cookie.getValue() : null;
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index cac365e..51c6182 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -32,6 +32,8 @@ import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.ThemeResource;
 
 import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
 import java.net.URI;
 
 /**
@@ -94,6 +96,13 @@ public class Urls {
         return identityProviderAuthnRequest(baseURI, providerId, realmName, null);
     }
 
+    public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode) {
+        return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
+                .path(IdentityBrokerService.class, "afterFirstBrokerLogin")
+                .replaceQueryParam(OAuth2Constants.CODE, accessCode)
+                .build(realmName);
+    }
+
     public static URI accountTotpPage(URI baseUri, String realmId) {
         return accountBase(baseUri).path(AccountService.class, "totpPage").build(realmId);
     }
@@ -204,6 +213,11 @@ public class Urls {
         return loginActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
     }
 
+    public static URI firstBrokerLoginProcessor(URI baseUri, String realmName) {
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "firstBrokerLoginGet")
+                .build(realmName);
+    }
+
     public static String localeCookiePath(URI baseUri, String realmName){
         return realmBase(baseUri).path(realmName).build().getRawPath();
     }
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index d3abc4d..0a3c9bb 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,8 +1,8 @@
 package org.keycloak.services.validation;
 
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
@@ -68,13 +68,13 @@ public class Validation {
     }
 
     public static List<FormMessage> validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
-        return validateUpdateProfileForm(null, formData);
+        return validateUpdateProfileForm(false, formData);
     }
 
-    public static List<FormMessage> validateUpdateProfileForm(RealmModel realm, MultivaluedMap<String, String> formData) {
+    public static List<FormMessage> validateUpdateProfileForm(boolean editUsernameAllowed, MultivaluedMap<String, String> formData) {
         List<FormMessage> errors = new ArrayList<>();
         
-        if (realm != null && realm.isEditUsernameAllowed() && isBlank(formData.getFirst(FIELD_USERNAME))) {
+        if (editUsernameAllowed && isBlank(formData.getFirst(FIELD_USERNAME))) {
             addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
         }
 
@@ -102,7 +102,7 @@ public class Validation {
      * @param user to validate
      * @return true if user object contains all mandatory values, false if some mandatory value is missing
      */
-    public static boolean validateUserMandatoryFields(RealmModel realm, UserModel user){
+    public static boolean validateUserMandatoryFields(RealmModel realm, UpdateProfileContext user){
         return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail()));        
     }
 
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index d6cada5..70551a1 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -9,3 +9,8 @@ org.keycloak.authentication.authenticators.resetcred.ResetCredentialChooseUser
 org.keycloak.authentication.authenticators.resetcred.ResetCredentialEmail
 org.keycloak.authentication.authenticators.resetcred.ResetOTP
 org.keycloak.authentication.authenticators.resetcred.ResetPassword
+org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
index a7bae96..f88fb4a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
@@ -52,7 +52,6 @@ public class IdentityProviderTest extends AbstractClientTest {
         assertEquals("clientSecret", representation.getConfig().get("clientSecret"));
         assertTrue(representation.isEnabled());
         assertFalse(representation.isStoreToken());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, representation.getUpdateProfileFirstLoginMode());
         assertFalse(representation.isTrustEmail());
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index d7084c1..8af43b2 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -23,15 +23,20 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory;
+import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.Urls;
@@ -138,7 +143,7 @@ public abstract class AbstractIdentityProviderTest {
     @Test
     public void testSuccessfulAuthentication() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
 
         UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
         Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
@@ -147,7 +152,7 @@ public abstract class AbstractIdentityProviderTest {
     @Test
     public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
 
         assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
     }
@@ -155,7 +160,7 @@ public abstract class AbstractIdentityProviderTest {
     @Test
     public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
 
         assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true);
     }
@@ -163,7 +168,7 @@ public abstract class AbstractIdentityProviderTest {
     @Test
     public void testSuccessfulAuthenticationWithoutUpdateProfile() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
 
         assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
     }
@@ -182,7 +187,7 @@ public abstract class AbstractIdentityProviderTest {
 
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
         try {
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
             identityProviderModel.setTrustEmail(false);
 
             UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false);
@@ -251,7 +256,7 @@ public abstract class AbstractIdentityProviderTest {
 
         try {
             IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
 
             UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false);
 
@@ -268,12 +273,12 @@ public abstract class AbstractIdentityProviderTest {
     @Test
     public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() {
         getRealm().setVerifyEmail(true);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
         brokerServerRule.stopSession(this.session, true);
         this.session = brokerServerRule.startSession();
 
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
         try {
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
             identityProviderModel.setTrustEmail(true);
 
             UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
@@ -300,7 +305,7 @@ public abstract class AbstractIdentityProviderTest {
 
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
         try {
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
             identityProviderModel.setTrustEmail(true);
 
             UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true);
@@ -320,7 +325,7 @@ public abstract class AbstractIdentityProviderTest {
 
         try {
             IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
 
             authenticateWithIdentityProvider(identityProviderModel, "test-user", false);
 
@@ -368,7 +373,7 @@ public abstract class AbstractIdentityProviderTest {
 
         try {
             IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
 
             authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false);
 
@@ -434,7 +439,8 @@ public abstract class AbstractIdentityProviderTest {
         loginPage.findSocialButton(getProviderId());
      }
 
-    @Test
+    // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour
+    // @Test
     public void testUserAlreadyExistsWhenUpdatingProfile() {
         this.driver.navigate().to("http://localhost:8081/test-app/");
 
@@ -469,11 +475,12 @@ public abstract class AbstractIdentityProviderTest {
         assertNotNull(federatedUser);
     }
 
-    @Test
+    // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour
+    // @Test
     public void testUserAlreadyExistsWhenNotUpdatingProfile() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
 
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
 
         this.driver.navigate().to("http://localhost:8081/test-app/");
 
@@ -507,6 +514,7 @@ public abstract class AbstractIdentityProviderTest {
 
         // Link my "pedroigor" identity with "test-user" from brokered Keycloak
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
         accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias());
 
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
@@ -607,6 +615,7 @@ public abstract class AbstractIdentityProviderTest {
 
     @Test
     public void testTokenStorageAndRetrievalByApplication() {
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
 
         identityProviderModel.setStoreToken(true);
@@ -772,7 +781,6 @@ public abstract class AbstractIdentityProviderTest {
 
         assertNotNull(identityProviderModel);
 
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
         identityProviderModel.setEnabled(true);
 
         return identityProviderModel;
@@ -849,4 +857,18 @@ public abstract class AbstractIdentityProviderTest {
 
         return htmlVerificationUrl;
     }
+
+    private void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) {
+        KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                RealmModel realm = session.realms().getRealm("realm-with-broker");
+                AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS);
+                reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin);
+                realm.updateAuthenticatorConfig(reviewProfileConfig);
+            }
+
+        });
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index 8914e7c..8e23f11 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -30,9 +30,9 @@ import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
 import org.keycloak.broker.saml.SAMLIdentityProvider;
 import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
 import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
-import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.social.facebook.FacebookIdentityProvider;
@@ -63,7 +63,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
     public void testInstallation() throws Exception {
         RealmModel realm = installTestRealm();
 
-        assertIdentityProviderConfig(realm.getIdentityProviders());
+        assertIdentityProviderConfig(realm, realm.getIdentityProviders());
 
         assertTrue(realm.isIdentityFederationEnabled());
         this.realmManager.removeRealm(realm);
@@ -81,10 +81,10 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         identityProviderModel.getConfig().put("config-added", "value-added");
         identityProviderModel.setEnabled(false);
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
         identityProviderModel.setTrustEmail(true);
         identityProviderModel.setStoreToken(true);
         identityProviderModel.setAuthenticateByDefault(true);
+        identityProviderModel.setFirstBrokerLoginFlowId(realm.getBrowserFlow().getId());
 
         realm.updateIdentityProvider(identityProviderModel);
 
@@ -96,14 +96,13 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         assertEquals("value-added", identityProviderModel.getConfig().get("config-added"));
         assertFalse(identityProviderModel.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, identityProviderModel.getUpdateProfileFirstLoginMode());
         assertTrue(identityProviderModel.isTrustEmail());
         assertTrue(identityProviderModel.isStoreToken());
         assertTrue(identityProviderModel.isAuthenticateByDefault());
+        assertEquals(identityProviderModel.getFirstBrokerLoginFlowId(), realm.getBrowserFlow().getId());
 
         identityProviderModel.getConfig().remove("config-added");
         identityProviderModel.setEnabled(true);
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING);
         identityProviderModel.setTrustEmail(false);
         identityProviderModel.setAuthenticateByDefault(false);
 
@@ -116,13 +115,12 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         assertFalse(identityProviderModel.getConfig().containsKey("config-added"));
         assertTrue(identityProviderModel.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_MISSING, identityProviderModel.getUpdateProfileFirstLoginMode());
         assertFalse(identityProviderModel.isTrustEmail());
         assertFalse(identityProviderModel.isAuthenticateByDefault());
         this.realmManager.removeRealm(realm);
     }
 
-    private void assertIdentityProviderConfig(List<IdentityProviderModel> identityProviders) {
+    private void assertIdentityProviderConfig(RealmModel realm, List<IdentityProviderModel> identityProviders) {
         assertFalse(identityProviders.isEmpty());
 
         Set<String> checkedProviders = new HashSet<String>(getExpectedProviders());
@@ -138,9 +136,9 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
                 } else if (OIDCIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
                     assertOidcIdentityProviderConfig(identityProvider);
                 } else if (FacebookIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
-                    assertFacebookIdentityProviderConfig(identityProvider);
+                    assertFacebookIdentityProviderConfig(realm, identityProvider);
                 } else if (GitHubIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
-                    assertGitHubIdentityProviderConfig(identityProvider);
+                    assertGitHubIdentityProviderConfig(realm, identityProvider);
                 } else if (TwitterIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
                     assertTwitterIdentityProviderConfig(identityProvider);
                 } else if (LinkedInIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
@@ -165,7 +163,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-google", config.getAlias());
         assertEquals(GoogleIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode());
         assertEquals(true, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(true, config.isStoreToken());
@@ -184,7 +181,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-saml-signed-idp", config.getAlias());
         assertEquals(SAMLIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isStoreToken());
@@ -205,7 +201,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-oidc-idp", config.getAlias());
         assertEquals(OIDCIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(false, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
@@ -213,37 +208,37 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("clientSecret", config.getClientSecret());
     }
 
-    private void assertFacebookIdentityProviderConfig(IdentityProviderModel identityProvider) {
+    private void assertFacebookIdentityProviderConfig(RealmModel realm, IdentityProviderModel identityProvider) {
         FacebookIdentityProvider facebookIdentityProvider = new FacebookIdentityProviderFactory().create(identityProvider);
         OAuth2IdentityProviderConfig config = facebookIdentityProvider.getConfig();
 
         assertEquals("model-facebook", config.getAlias());
         assertEquals(FacebookIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
+        assertEquals(realm.getBrowserFlow().getId(), identityProvider.getFirstBrokerLoginFlowId());
         assertEquals(FacebookIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
         assertEquals(FacebookIdentityProvider.TOKEN_URL, config.getTokenUrl());
         assertEquals(FacebookIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
     }
 
-    private void assertGitHubIdentityProviderConfig(IdentityProviderModel identityProvider) {
+    private void assertGitHubIdentityProviderConfig(RealmModel realm, IdentityProviderModel identityProvider) {
         GitHubIdentityProvider gitHubIdentityProvider = new GitHubIdentityProviderFactory().create(identityProvider);
         OAuth2IdentityProviderConfig config = gitHubIdentityProvider.getConfig();
 
         assertEquals("model-github", config.getAlias());
         assertEquals(GitHubIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
+        assertEquals(realm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW).getId(), identityProvider.getFirstBrokerLoginFlowId());
         assertEquals(GitHubIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
         assertEquals(GitHubIdentityProvider.TOKEN_URL, config.getTokenUrl());
         assertEquals(GitHubIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
@@ -254,17 +249,16 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         OAuth2IdentityProviderConfig config = liIdentityProvider.getConfig();
 
         assertEquals("model-linkedin", config.getAlias());
-      assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
-      assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_MISSING, config.getUpdateProfileFirstLoginMode());
+        assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+        assertEquals(true, config.isEnabled());
         assertEquals(false, config.isTrustEmail());
-      assertEquals(false, config.isAuthenticateByDefault());
-      assertEquals(false, config.isStoreToken());
-      assertEquals("clientId", config.getClientId());
-      assertEquals("clientSecret", config.getClientSecret());
-      assertEquals(LinkedInIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
-      assertEquals(LinkedInIdentityProvider.TOKEN_URL, config.getTokenUrl());
-      assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(false, config.isStoreToken());
+        assertEquals("clientId", config.getClientId());
+        assertEquals("clientSecret", config.getClientSecret());
+        assertEquals(LinkedInIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
+        assertEquals(LinkedInIdentityProvider.TOKEN_URL, config.getTokenUrl());
+        assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
     }
 
     private void assertStackoverflowIdentityProviderConfig(IdentityProviderModel identityProvider) {
@@ -274,7 +268,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-stackoverflow", config.getAlias());
         assertEquals(StackoverflowIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
@@ -293,7 +286,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-twitter", config.getAlias());
         assertEquals(TwitterIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(true, config.isStoreToken());
diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
index 410d373..0b8b79e 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
@@ -17,7 +17,6 @@
             "alias" : "model-google",
             "providerId" : "google",
             "enabled": true,
-            "updateProfileFirstLogin" : "true",
             "trustEmail" : "true",
             "storeToken": "true",
             "config": {
@@ -29,7 +28,7 @@
             "alias" : "model-facebook",
             "providerId" : "facebook",
             "enabled": true,
-            "updateProfileFirstLogin" : "false",
+            "firstBrokerLoginFlowAlias" : "browser",
             "config": {
                 "authorizationUrl": "authorizationUrl",
                 "tokenUrl": "tokenUrl",
@@ -42,7 +41,6 @@
             "alias" : "model-github",
             "providerId" : "github",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "on",
             "storeToken": "false",
             "config": {
                 "authorizationUrl": "authorizationUrl",
@@ -56,7 +54,6 @@
             "alias" : "model-twitter",
             "providerId" : "twitter",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "off",
             "storeToken": true,
             "config": {
                 "authorizationUrl": "authorizationUrl",
@@ -70,7 +67,6 @@
             "alias" : "model-linkedin",
             "providerId" : "linkedin",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "missing",
             "storeToken": false,
             "config": {
                 "authorizationUrl": "authorizationUrl",
@@ -84,7 +80,6 @@
             "alias" : "model-stackoverflow",
             "providerId" : "stackoverflow",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "off",
             "storeToken": false,
             "config": {
                 "key": "keyValue",
@@ -99,7 +94,6 @@
           "alias" : "model-saml-signed-idp",
           "providerId" : "saml",
           "enabled": true,
-          "updateProfileFirstLoginMode" : "on",
           "config": {
             "singleSignOnServiceUrl": "http://localhost:8082/auth/realms/realm-with-saml-identity-provider/protocol/saml",
             "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
@@ -115,7 +109,6 @@
             "alias" : "kc-saml-signed-idp",
             "providerId" : "saml",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "on",
             "addReadTokenRoleOnCreate": true,
             "config": {
                 "singleSignOnServiceUrl": "http://localhost:8082/auth/realms/realm-with-saml-signed-idp/protocol/saml",
@@ -134,7 +127,6 @@
             "alias" : "kc-saml-idp-basic",
             "providerId" : "saml",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "on",
             "trustEmail" : false,
             "addReadTokenRoleOnCreate": true,
             "config": {
@@ -150,7 +142,6 @@
             "alias" : "model-oidc-idp",
             "providerId" : "oidc",
             "enabled": false,
-            "updateProfileFirstLoginMode" : "off",
             "authenticateByDefault" : "false",
             "config": {
                 "clientId": "clientId",
@@ -166,7 +157,6 @@
             "alias" : "kc-oidc-idp",
             "providerId" : "keycloak-oidc",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "off",
             "storeToken" : true,
             "addReadTokenRoleOnCreate": true,
             "config": {
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml b/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml
index fc29132..e6c72f3 100644
--- a/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml
@@ -12,8 +12,6 @@
     <name>Adapter Tests on Karaf</name>
     
     <properties>
-        <karaf.version>3.0.3</karaf.version>
-        <karaf.home>${project.build.directory}/assembly</karaf.home>
                 
         <!--fuse examples expect auth server on 8080-->
         <auth.server.port.offset>0</auth.server.port.offset>
@@ -25,83 +23,111 @@
     </properties>
 
     <dependencies>
-        <!-- for karaf-maven-plugin -->
-        <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>framework</artifactId>
-            <version>${karaf.version}</version>
-            <type>kar</type>
-        </dependency>
+
         <dependency>
-            <groupId>org.apache.camel.karaf</groupId>
-            <artifactId>apache-camel</artifactId>
-            <version>2.12.5</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>org.jboss.arquillian.container</groupId>
+            <artifactId>arquillian-container-karaf-managed</artifactId>
+            <version>2.1.0.CR18</version>
         </dependency>
+        
         <dependency>
-            <groupId>org.apache.cxf.karaf</groupId>
-            <artifactId>apache-cxf</artifactId>
-            <version>2.7.14</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>org.apache.aries.jmx</groupId>
+            <artifactId>org.apache.aries.jmx</artifactId>
+            <version>1.1.1</version>
         </dependency>
+        
         <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-osgi-features</artifactId>
-            <version>${project.version}</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>jline</groupId>
+            <artifactId>jline</artifactId>
+            <version>2.12</version>
         </dependency>
         <dependency>
-            <groupId>org.keycloak.example.demo</groupId>
-            <artifactId>keycloak-fuse-example-features</artifactId>
-            <version>${project.version}</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+            <version>0.12.0</version>
         </dependency>
-        <!-- for arquillian -->
         <dependency>
-            <groupId>org.jboss.arquillian.container</groupId>
-            <artifactId>arquillian-container-karaf-managed</artifactId>
-            <version>2.1.0.CR18</version>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <version>3.0.3</version>
         </dependency>
         <dependency>
-            <groupId>org.apache.aries.jmx</groupId>
-            <artifactId>org.apache.aries.jmx</artifactId>
-            <version>1.1.1</version>
-        </dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.client</artifactId>
+            <version>3.0.3</version>
+        </dependency>  
+        
     </dependencies>
-    
+
+    <profiles>
+        
+        <profile>
+            <id>fuse</id>
+            <activation>
+                <property>
+                    <name>fuse.home</name>
+                </property>
+            </activation>
+            <properties>
+                <karaf.home>${fuse.home}</karaf.home>
+                <arquillian.xml.stylesheet>src/main/xslt/arquillian-fuse.xsl</arquillian.xml.stylesheet>
+            </properties>
+        </profile>
+        
+        <profile>
+            <id>karaf</id>
+            <activation>
+                <property>
+                    <name>!fuse.home</name>
+                </property>
+            </activation>
+            <properties>
+                <karaf.version>3.0.3</karaf.version>
+                <karaf.home>${containers.home}/apache-karaf-minimal-${karaf.version}</karaf.home>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.karaf</groupId>
+                    <artifactId>apache-karaf-minimal</artifactId>
+                    <version>${karaf.version}</version>
+                    <type>zip</type>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-dependency-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>unpack-karaf</id>
+                                <phase>generate-resources</phase>
+                                <goals>
+                                    <goal>unpack</goal>
+                                </goals>
+                                <configuration>
+                                    <artifactItems>
+                                        <artifactItem>
+                                            <groupId>org.apache.karaf</groupId>
+                                            <artifactId>apache-karaf-minimal</artifactId>
+                                            <version>${karaf.version}</version>
+                                            <type>zip</type>
+                                            <outputDirectory>${containers.home}</outputDirectory>
+                                        </artifactItem>
+                                    </artifactItems>
+                                    <overWriteIfNewer>true</overWriteIfNewer>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>        
+        </profile>
+    </profiles>
+
     <build>
         <plugins>
             <plugin>
-                <groupId>org.apache.karaf.tooling</groupId>
-                <artifactId>karaf-maven-plugin</artifactId>
-                <version>${karaf.version}</version>
-                <extensions>true</extensions>
-                <executions>
-                    <execution>
-                        <id>prepare-karaf-with-examples</id>
-                        <phase>generate-test-resources</phase>
-                        <goals>
-                            <!-- creates custom karaf distro in ${project.build.directory}/assembly -->
-                            <goal>install-kars</goal>
-                        </goals>
-                    </execution>
-                </executions>
-                <configuration>
-                    <bootFeatures>
-                        <!-- this installs all fuse examples -->
-                        <feature>keycloak-fuse-example</feature>
-                    </bootFeatures>
-                </configuration>
-            </plugin>
-            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
@@ -117,5 +143,5 @@
             </plugin>
         </plugins>
     </build>
-
+            
 </project>
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainer.java b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainer.java
new file mode 100644
index 0000000..e9f1212
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainer.java
@@ -0,0 +1,114 @@
+package org.keycloak.testsuite.arquillian.karaf;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import org.jboss.arquillian.container.osgi.jmx.ObjectNameFactory;
+import org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer;
+import org.jboss.arquillian.container.spi.client.container.LifecycleException;
+import org.jboss.logging.Logger;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class CustomKarafContainer extends KarafManagedDeployableContainer<CustomKarafContainerConfiguration> {
+
+    protected final Logger log = Logger.getLogger(this.getClass());
+
+    private CustomKarafContainerConfiguration config;
+
+    protected MBeanServerConnection mbeanServer = null;
+    protected ObjectName feature;
+
+    @Override
+    public void start() throws LifecycleException {
+        super.start();
+        executeAfterStartCommands();
+    }
+
+    @Override
+    public void setup(CustomKarafContainerConfiguration config) {
+        super.setup(config);
+        this.config = config;
+    }
+
+    @Override
+    public Class<CustomKarafContainerConfiguration> getConfigurationClass() {
+        return CustomKarafContainerConfiguration.class;
+    }
+
+    protected void executeAfterStartCommands() throws LifecycleException {
+        try {
+            mbeanServer = getMBeanServerConnection(500, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            throw new LifecycleException("JMX connection timed out.");
+        }
+
+        try {
+
+            feature = ObjectNameFactory.create("org.apache.karaf:type=feature,name=root");
+            try {
+                mbeanServer.getObjectInstance(feature);
+            } catch (InstanceNotFoundException infe) {
+                try {
+                    feature = ObjectNameFactory.create("org.apache.karaf:type=features,name=root");
+                    mbeanServer.getObjectInstance(feature);
+                } catch (InstanceNotFoundException infe2) {
+                    throw new RuntimeException("Feature MBean not found on server.");
+                }
+            }
+
+            featureMBean = getMBeanProxy(mbeanServer, feature, FeatureMBean.class, 30, TimeUnit.SECONDS);
+
+            log.info("Executing karaf after-start commands");
+            for (String command : config.getCommandsAfterStartAsArray()) {
+                String cmd = command.trim().split(" ")[0].trim();
+                String param = command.trim().split(" ")[1].trim();
+                log.info(String.format("command: %s, param: %s", cmd, param));
+                switch (cmd) {
+                    case "feature:repo-add":
+                    case "features:addurl":
+                        featureMBean.addRepository(param);
+                        break;
+                    case "feature:repo-remove":
+                    case "features:removeurl":
+                        featureMBean.removeRepository(param);
+                        break;
+                    case "feature:install":
+                    case "features:install":
+                        featureMBean.installFeature(param);
+                        break;
+                    case "feature:uninstall":
+                    case "features:uninstall":
+                        featureMBean.uninstallFeature(param);
+                        break;
+                    default:
+                        throw new RuntimeException(String.format("Unsupported command: '%s'. "
+                                + "Supported after-start commands for Karaf: 'feature:repo-add', 'feature:install', 'feature:repo-remove', 'feature:uninstall'\n"
+                                + "Supported after-start commands for Fuse: 'features:addurl', 'features:install', 'features:removeurl', 'features:uninstall'", cmd));
+                }
+            }
+        } catch (IOException | RuntimeException | TimeoutException ex) {
+            stop();
+            throw new LifecycleException("Error when executing karaf after-start commands.", ex);
+        }
+    }
+
+    FeatureMBean featureMBean;
+
+    public interface FeatureMBean {
+
+        public void addRepository(String repository);
+
+        public void removeRepository(String repository);
+
+        public void installFeature(String feature);
+
+        public void uninstallFeature(String feature);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerConfiguration.java b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerConfiguration.java
new file mode 100644
index 0000000..05d063c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerConfiguration.java
@@ -0,0 +1,25 @@
+package org.keycloak.testsuite.arquillian.karaf;
+
+import org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedContainerConfiguration;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class CustomKarafContainerConfiguration extends KarafManagedContainerConfiguration {
+
+    private String commandsAfterStart;
+
+    public String getCommandsAfterStart() {
+        return commandsAfterStart;
+    }
+
+    public String[] getCommandsAfterStartAsArray() {
+        return getCommandsAfterStart().trim().split(",");
+    }
+
+    public void setCommandsAfterStart(String commandsAfterStart) {
+        this.commandsAfterStart = commandsAfterStart;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerExtension.java b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerExtension.java
new file mode 100644
index 0000000..3a5611f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerExtension.java
@@ -0,0 +1,17 @@
+package org.keycloak.testsuite.arquillian.karaf;
+
+import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
+import org.jboss.arquillian.core.spi.LoadableExtension;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class CustomKarafContainerExtension implements LoadableExtension {
+
+    @Override
+    public void register(ExtensionBuilder builder) {
+        builder.service(DeployableContainer.class, CustomKarafContainer.class);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
new file mode 100644
index 0000000..2c03587
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
@@ -0,0 +1 @@
+org.keycloak.testsuite.arquillian.karaf.CustomKarafContainerExtension
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl
index fabd47b..3d6f718 100644
--- a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl
@@ -4,6 +4,8 @@
                 version="2.0"
                 exclude-result-prefixes="xalan a">
 
+    <xsl:param name="keycloak.version" />
+    
     <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
     <xsl:strip-space elements="*"/>
 
@@ -14,13 +16,24 @@
             <container qualifier="app-server-karaf" mode="manual" >
                 <configuration>
                     <property name="enabled">${app.server.karaf}</property>
-                    <property name="adapterImplClass">org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer</property>
+                    <!--<property name="adapterImplClass">org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer</property>-->
+                    <property name="adapterImplClass">org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer</property>
                     <property name="autostartBundle">false</property>
                     <property name="karafHome">${karaf.home}</property>
                     <property name="javaVmArguments">-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n ${adapter.test.props}</property>
                     <property name="jmxServiceURL">service:jmx:rmi://127.0.0.1:44444/jndi/rmi://127.0.0.1:1099/karaf-root</property>
                     <property name="jmxUsername">karaf</property>
-                    <property name="jmxPassword">karaf</property>            
+                    <property name="jmxPassword">karaf</property>          
+                   
+                    <!-- The following commands are performed by the CustomKarafContainer -->
+                    <property name="commandsAfterStart">
+                        feature:repo-add mvn:org.apache.camel.karaf/apache-camel/2.15.1/xml/features,
+                        feature:repo-add mvn:org.apache.cxf.karaf/apache-cxf/3.0.4/xml/features,
+                        feature:repo-add mvn:org.keycloak/keycloak-osgi-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        feature:repo-add mvn:org.keycloak.example.demo/keycloak-fuse-example-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        feature:install keycloak-fuse-example
+                    </property>
+                          
                 </configuration>
             </container>
     
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian-fuse.xsl b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian-fuse.xsl
new file mode 100644
index 0000000..6c6345e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian-fuse.xsl
@@ -0,0 +1,47 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:a="http://jboss.org/schema/arquillian"
+                version="2.0"
+                exclude-result-prefixes="xalan a">
+
+    <xsl:param name="keycloak.version" />
+    
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:template match="/a:arquillian">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            
+            <container qualifier="app-server-karaf" mode="manual" >
+                <configuration>
+                    <property name="enabled">${app.server.karaf}</property>
+                    <property name="adapterImplClass">org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer</property>
+                    <property name="autostartBundle">false</property>
+                    <property name="karafHome">${karaf.home}</property>
+                    <property name="javaVmArguments">-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n ${adapter.test.props}</property>
+                    <property name="jmxServiceURL">service:jmx:rmi://127.0.0.1:44444/jndi/rmi://127.0.0.1:1099/karaf-root</property>
+                    <property name="jmxUsername">admin</property>
+                    <property name="jmxPassword">admin</property>          
+                    
+                    <property name="commandsAfterStart">
+                        features:addurl mvn:org.keycloak/keycloak-osgi-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        features:addurl mvn:org.keycloak.example.demo/keycloak-fuse-example-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        features:install keycloak-fuse-example
+                    </property>
+                    
+                </configuration>
+            </container>
+    
+        </xsl:copy>
+    </xsl:template>
+    
+
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+    
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/adapters/pom.xml b/testsuite/integration-arquillian/tests/adapters/pom.xml
index 1e4fdfa..ee81124 100644
--- a/testsuite/integration-arquillian/tests/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/pom.xml
@@ -19,6 +19,8 @@
         <app.server.management.port.jmx>10199</app.server.management.port.jmx>
         <adapter.test.props>-Dapp.server.base.url=http://localhost:${app.server.http.port} -Dmy.host.name=localhost</adapter.test.props>
         <exclude.adapters>-</exclude.adapters>
+        
+        <arquillian.xml.stylesheet>src/main/xslt/arquillian.xsl</arquillian.xml.stylesheet>
     </properties>
     
     <build>
@@ -66,7 +68,13 @@
                                         <includes>
                                             <include>arquillian.xml</include>
                                         </includes>
-                                        <stylesheet>src/main/xslt/arquillian.xsl</stylesheet>
+                                        <stylesheet>${arquillian.xml.stylesheet}</stylesheet>
+                                        <parameters>
+                                            <parameter>
+                                                <name>keycloak.version</name>
+                                                <value>${project.version}</value>
+                                            </parameter>
+                                        </parameters>
                                         <outputDir>${project.build.directory}/dependency</outputDir>
                                     </transformationSet>
                                 </transformationSets>
diff --git a/testsuite/integration-arquillian/tests/adapters/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java
new file mode 100644
index 0000000..3f1aa7d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java
@@ -0,0 +1,14 @@
+package org.keycloak.testsuite.adapter.example;
+
+import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly")
+@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyDemoExampleAdapterTest extends AbstractDemoExampleAdapterTest {
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
index a196fc7..78c8f6f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -17,21 +17,22 @@
  */
 package org.keycloak.testsuite.admin;
 
+import org.jboss.logging.Logger;
+import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 
 import javax.ws.rs.core.Response;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import org.jboss.logging.Logger;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.UserResource;
-import org.keycloak.representations.idm.CredentialRepresentation;
+
 import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
-import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
 
 /**
  * Created by st on 28.05.15.
@@ -58,6 +59,15 @@ public class ApiUtil {
         return null;
     }
 
+    public static ClientResource findClientResourceByName(RealmResource realm, String name) {
+        for (ClientRepresentation c : realm.clients().findAll()) {
+            if (c.getName().equals(name)) {
+                return realm.clients().get(c.getId());
+            }
+        }
+        return null;
+    }
+
     public static ClientRepresentation findClientByClientId(RealmResource realm, String clientId) {
         ClientRepresentation client = null;
         for (ClientRepresentation c : realm.clients().findAll()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java
index 6727108..9b68c43 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java
@@ -123,7 +123,8 @@ public class ContainersTestEnricher {
      */
     private void checkServerLog() throws IOException {
         Container container = containers.removeFirst();
-        if (!container.getName().equals("auth-server-undertow")) {
+        if (container.getName().equals("auth-server-wildfly")
+                || container.getName().matches("auth-server-eap.")) {
             String jbossHomePath = container.getContainerConfiguration().getContainerProperties().get("jbossHome");
             log.debug("jbossHome: " + jbossHomePath + "\n");
 
@@ -133,8 +134,8 @@ public class ContainersTestEnricher {
                     = serverLogContent.contains("ERROR")
                     || serverLogContent.contains("SEVERE")
                     || serverLogContent.contains("Exception ");
-                    //There is expected string "Exception" in server log: Adding provider 
-                    //singleton org.keycloak.services.resources.ModelExceptionMapper
+            //There is expected string "Exception" in server log: Adding provider 
+            //singleton org.keycloak.services.resources.ModelExceptionMapper
 
             if (containsError) {
                 throw new RuntimeException(container.getName() + ": Server log contains ERROR.");
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
index a987a16..d23d4b5 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
@@ -9,7 +9,6 @@ import org.jboss.arquillian.core.spi.LoadableExtension;
 import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
 import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
 import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
-import org.keycloak.testsuite.arquillian.jira.JiraTestExecutionDecider;
 import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
 import org.keycloak.testsuite.arquillian.undertow.CustomUndertowContainer;
 
@@ -37,7 +36,6 @@ public class KeycloakArquillianExtension implements LoadableExtension {
                 .service(DeployableContainer.class, CustomUndertowContainer.class);
 
         builder
-                .service(TestExecutionDecider.class, JiraTestExecutionDecider.class)
                 .service(TestExecutionDecider.class, MigrationTestExecutionDecider.class);
 
         builder
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java
index 5623198..7e50272 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java
@@ -17,14 +17,16 @@
  */
 package org.keycloak.testsuite.auth.page.account;
 
-import java.util.List;
-import javax.ws.rs.core.UriBuilder;
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
+import javax.ws.rs.core.UriBuilder;
+import java.util.List;
+
 /**
- *
  * @author Petr Mensik
+ * @author mhajas
  */
 public class Applications extends AccountManagement {
 
@@ -39,26 +41,42 @@ public class Applications extends AccountManagement {
     @FindBy(xpath = XPATH_APP_TABLE)
     protected WebElement appTable;
 
-    @FindBy(xpath = XPATH_APP_TABLE + "//a")
-    protected List<WebElement> applicationLinks;
-    
+    @FindBy(xpath = XPATH_APP_TABLE + "//tr")
+    private List<WebElement> applicationRows;
+
     public boolean containsApplication(String application) {
-        boolean contains = false;
-        for (WebElement appLink : applicationLinks) {
-            if (appLink.getText().equals(application)) {
-                contains = true;
-                break;
-            }
-        }
-        return contains;
+        return getRowForLinkText(application) != null;
     }
-    
+
     public void clickApplication(String application) {
-        for (WebElement appLink : applicationLinks) {
-            if (appLink.getText().equals(application)) {
-                appLink.click();
+        WebElement row = getRowForLinkText(application);
+        if (row == null) {
+            log.error("Application: " + application + " doesn't exist");
+            throw new IllegalArgumentException("Application: " + application + " doesn't exist");
+        }
+
+        row.findElement(By.xpath(".//a")).click();
+    }
+
+    public void revokeGrantForApplication(String application) {
+        WebElement row = getRowForLinkText(application);
+        if (row == null) {
+            log.error("Application: " + application + " doesn't exist");
+            throw new IllegalArgumentException("Application: " + application + " doesn't exist");
+        }
+
+        row.findElement(By.xpath("//button[@id='revoke-" + application + "']")).click();
+    }
+
+    private WebElement getRowForLinkText(String appLink) {
+        for (WebElement appRow : applicationRows) {
+            if (appRow.findElement(By.xpath(".//td")).getText().equals(appLink)) {
+                return appRow;
             }
         }
+
+        return null;
     }
 
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java
index e8a435c..f6ba6b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java
@@ -1,15 +1,16 @@
 package org.keycloak.testsuite.auth.page;
 
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
-import java.net.URI;
+
 import javax.ws.rs.core.UriBuilder;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import java.net.URI;
 
 /**
  * Keycloak realm.
- * 
+ * <p>
  * URL: http://localhost:${auth.server.http.port}/auth/realms/{authRealm}
- * 
+ *
  * @author tkyjovsk
  */
 public class AuthRealm extends AuthServer implements PageWithLoginUrl {
@@ -22,7 +23,7 @@ public class AuthRealm extends AuthServer implements PageWithLoginUrl {
     public static final String EXAMPLE = "example";
 
     public static final String ADMIN = "admin";
-    
+
     public AuthRealm() {
         setUriParameter(AUTH_REALM, MASTER);
     }
@@ -46,7 +47,6 @@ public class AuthRealm extends AuthServer implements PageWithLoginUrl {
     }
 
     /**
-     *
      * @return OIDC Login URL for authRealm
      */
     @Override
@@ -55,4 +55,9 @@ public class AuthRealm extends AuthServer implements PageWithLoginUrl {
                 .build(getAuthRealm());
     }
 
+    public URI getOIDCLogoutUrl() {
+        return OIDCLoginProtocolService.logoutUrl(UriBuilder.fromPath(getAuthRoot()))
+                .build(getAuthRealm());
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
new file mode 100644
index 0000000..e328014
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.auth.page.login;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthGrant extends LoginActions {
+
+    @FindBy(css = "input[name=\"accept\"]")
+    private WebElement acceptButton;
+    @FindBy(css = "input[name=\"cancel\"]")
+    private WebElement cancelButton;
+
+
+    public void accept() {
+        acceptButton.click();
+    }
+
+    public void cancel() {
+        cancelButton.click();
+    }
+
+    @Override
+    public boolean isCurrent() {
+        return driver.getTitle().equals("OAuth Grant");
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java
index 5cd74bf..05b0024 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java
@@ -3,6 +3,7 @@ package org.keycloak.testsuite.console.page.authentication;
 import org.keycloak.testsuite.console.page.AdminConsoleRealm;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
+import static org.keycloak.testsuite.util.WaitUtils.waitAjaxForElement;
 
 /**
  * @author tkyjovsk
@@ -13,6 +14,22 @@ public class Authentication extends AdminConsoleRealm {
     @FindBy(xpath = "//h1[text()='Authentication']/..")
     private AuthenticationTabs authenticationTabs;
 
+    @FindBy(xpath = "//div[contains(@class, 'alert-danger')]")
+    private WebElement error;
+    
+    @FindBy(xpath = "//div[contains(@class, 'alert-success')]")
+    private WebElement success;
+    
+    public String getSuccessMessage() {
+        waitAjaxForElement(success);
+        return success.getText();
+    }
+    
+    public String getErrorMessage() {
+        waitAjaxForElement(error);
+        return error.getText();
+    }
+    
     public AuthenticationTabs tabs() {
         return authenticationTabs;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/Bindings.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/Bindings.java
new file mode 100644
index 0000000..12bb9c1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/Bindings.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.bindings;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class Bindings extends Authentication {
+
+    @Override
+    public String getUriFragment() {
+        return super.getUriFragment() + "/flow-bindings";
+    }
+
+    @Page
+    private BindingsForm form;
+    
+    public BindingsForm form() {
+        return form;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/BindingsForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/BindingsForm.java
new file mode 100644
index 0000000..d3e2f66
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/BindingsForm.java
@@ -0,0 +1,95 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.bindings;
+
+import org.keycloak.testsuite.console.page.authentication.bindings.BindingsForm.BindingsSelect;
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class BindingsForm extends Form {
+    
+    @FindBy(id = "browser")
+    private Select browser;
+
+    @FindBy(id = "registration")
+    private Select registration;
+
+    @FindBy(id = "grant")
+    private Select grant;
+
+    @FindBy(id = "resetCredentials")
+    private Select resetCredentials;
+
+    @FindBy(id = "clientAuthentication")
+    private Select clientAuthentication;
+
+    public void select(BindingsSelect select, BindingsOption option) {
+        switch (select) {
+            case BROWSER:
+                browser.selectByVisibleText(option.getName());
+                break;
+            case REGISTRATION:
+                registration.selectByVisibleText(option.getName());
+                break;
+            case DIRECT_GRANT:
+                grant.selectByVisibleText(option.getName());
+                break;
+            case RESET_CREDENTIALS:
+                resetCredentials.selectByVisibleText(option.getName());
+                break;
+            case CLIENT_AUTHENTICATION:
+                clientAuthentication.selectByVisibleText(option.getName());
+                break;
+        }
+    }
+
+    public enum BindingsSelect {
+        BROWSER, 
+        REGISTRATION, 
+        DIRECT_GRANT,
+        RESET_CREDENTIALS,
+        CLIENT_AUTHENTICATION;
+    }
+    
+    public enum BindingsOption {
+
+        DIRECT_GRANT("direct grant"), 
+        REGISTRATION("registration"), 
+        BROWSER("browser"),
+        RESET_CREDENTIALS("reset credentials");
+
+        private final String name;
+
+        private BindingsOption(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicy.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicy.java
new file mode 100644
index 0000000..814ae54
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicy.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.otppolicy;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class OTPPolicy extends Authentication {
+
+    @Override
+    public String getUriFragment() {
+        return super.getUriFragment() + "/otp-policy";
+    }
+    
+    @Page
+    private OTPPolicyForm form;
+    
+    public OTPPolicyForm form() {
+        return form;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
new file mode 100644
index 0000000..1e88cb3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
@@ -0,0 +1,119 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.otppolicy;
+
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class OTPPolicyForm extends Form {
+    
+    @FindBy(id = "type")
+    private Select otpType;
+    
+    @FindBy(id = "alg")
+    private Select otpHashAlg;
+    
+    @FindBy(id = "digits")
+    private Select digits;
+    
+    @FindBy(id = "lookAhead")
+    private WebElement lookAhead;
+    
+    @FindBy(id = "period")
+    private WebElement period;
+    
+    @FindBy(id = "counter")
+    private WebElement counter;
+    
+    public void setValues(OTPType otpType, OTPHashAlg otpHashAlg, Digits digits, String lookAhead, String periodOrCounter) {
+        this.otpType.selectByValue(otpType.getName());
+        this.otpHashAlg.selectByValue(otpHashAlg.getName());
+        this.digits.selectByValue(digits.getName());
+        setInputValue(this.lookAhead, lookAhead);
+        
+        switch (otpType) {
+            case TIME_BASED:
+                setInputValue(period, periodOrCounter);
+                break;
+            case COUNTER_BASED:
+                setInputValue(counter, periodOrCounter);
+                break;
+        }
+        save();
+    }
+    
+    public enum OTPType {
+
+        TIME_BASED("totp"),
+        COUNTER_BASED("hotp");
+
+        private final String name;
+
+        private OTPType(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+    
+    public enum OTPHashAlg {
+
+        SHA1("HmacSHA1"),
+        SHA256("HmacSHA256"),
+        SHA512("HmacSHA512");
+
+        private final String name;
+
+        private OTPHashAlg(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+    
+    public enum Digits {
+
+        EMPTY("? number:6 ?"),
+        SIX("6"),
+        EIGHT("8");
+
+        private final String name;
+
+        private Digits(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java
index 41cdf71..589e68e 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java
@@ -1,15 +1,17 @@
 package org.keycloak.testsuite.console.page.clients;
 
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
 import java.util.ArrayList;
 import java.util.List;
-import org.keycloak.representations.idm.ClientRepresentation;
+
 import static org.keycloak.testsuite.auth.page.login.Login.OIDC;
 import static org.keycloak.testsuite.util.WaitUtils.pause;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
 
 /**
- *
  * @author tkyjovsk
  */
 public class ClientSettingsForm extends CreateClientForm {
@@ -26,6 +28,9 @@ public class ClientSettingsForm extends CreateClientForm {
     @FindBy(xpath = ".//i[contains(@data-ng-click, 'deleteWebOrigin')]")
     private List<WebElement> deleteWebOriginIcons;
 
+    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
+    private OnOffSwitch consentRequired;
+
     public void setBaseUrl(String baseUrl) {
         setInputValue(baseUrlInput, baseUrl);
     }
@@ -88,4 +93,8 @@ public class ClientSettingsForm extends CreateClientForm {
         return values;
     }
 
+    public void setConsentRequired(boolean value) {
+        consentRequired.setOn(value);
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java
index 72db4bb..24933cb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java
@@ -10,7 +10,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
 import org.keycloak.testsuite.adapter.page.AngularCorsProductExample;
 import org.keycloak.testsuite.adapter.page.CorsDatabaseServiceExample;
-import org.keycloak.testsuite.arquillian.jira.Jira;
 import org.keycloak.testsuite.auth.page.account.Account;
 
 import java.io.File;
@@ -63,7 +62,6 @@ public abstract class AbstractCorsExampleAdapterTest extends AbstractExampleAdap
         driver.manage().deleteAllCookies();
     }
 
-    @Jira("KEYCLOAK-1546")
     @Test
     public void angularCorsProductTest() {
         angularCorsProductExample.navigateTo();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
index d31d390..4d0eed9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
@@ -1,34 +1,71 @@
 package org.keycloak.testsuite.adapter.example;
 
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
-import org.junit.*;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.testsuite.auth.page.account.Account;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
 import org.keycloak.testsuite.adapter.page.CustomerPortalExample;
 import org.keycloak.testsuite.adapter.page.DatabaseServiceExample;
 import org.keycloak.testsuite.adapter.page.ProductPortalExample;
-import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Account;
+import org.keycloak.testsuite.auth.page.account.Applications;
+import org.keycloak.testsuite.auth.page.login.OAuthGrant;
+import org.keycloak.testsuite.console.page.clients.ClientSettings;
+import org.keycloak.testsuite.console.page.clients.Clients;
+import org.keycloak.testsuite.console.page.events.Config;
+import org.keycloak.testsuite.console.page.events.LoginEvents;
 import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 
 public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdapterTest {
 
     @Page
-    private CustomerPortalExample customerPortalExample;
+    private CustomerPortalExample customerPortalExamplePage;
+
     @Page
-    private ProductPortalExample productPortalExample;
+    private ProductPortalExample productPortalExamplePage;
+
     @Page
-    private DatabaseServiceExample databaseServiceExample;
+    private DatabaseServiceExample databaseServiceExamplePage;
 
     @Page
-    private Account testRealmAccount;
+    private Account testRealmAccountPage;
+
+    @Page
+    private Clients clientsPage;
+
+    @Page
+    private ClientSettings clientSettingsPage;
+
+    @Page
+    private Config configPage;
+
+    @Page
+    private LoginEvents loginEventsPage;
+
+    @Page
+    private OAuthGrant oAuthGrantPage;
+
+    @Page
+    private Applications applicationsPage;
 
     @Deployment(name = CustomerPortalExample.DEPLOYMENT_NAME)
     private static WebArchive customerPortalExample() throws IOException {
@@ -56,27 +93,31 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
         super.setDefaultPageUriParameters();
         testRealmPage.setAuthRealm(DEMO);
         testRealmLoginPage.setAuthRealm(DEMO);
-        testRealmAccount.setAuthRealm(DEMO);
+        testRealmAccountPage.setAuthRealm(DEMO);
+        clientsPage.setConsoleRealm(DEMO);
+        configPage.setConsoleRealm(DEMO);
+        loginEventsPage.setConsoleRealm(DEMO);
+        applicationsPage.setAuthRealm(DEMO);
     }
 
     @Before
     public void beforeDemoExampleTest() {
-        customerPortalExample.navigateTo();
+        customerPortalExamplePage.navigateTo();
         driver.manage().deleteAllCookies();
-        productPortalExample.navigateTo();
+        productPortalExamplePage.navigateTo();
         driver.manage().deleteAllCookies();
     }
 
     @Test
     public void customerPortalListingTest() {
 
-        customerPortalExample.navigateTo();
-        customerPortalExample.customerListing();
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(customerPortalExample);
-        customerPortalExample.waitForCustomerListingHeader();
+        assertCurrentUrlStartsWith(customerPortalExamplePage);
+        customerPortalExamplePage.waitForCustomerListingHeader();
 
         Assert.assertTrue(driver.getPageSource().contains("Username: bburke@redhat.com"));
         Assert.assertTrue(driver.getPageSource().contains("Bill Burke"));
@@ -86,72 +127,140 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
     @Test
     public void customerPortalSessionTest() {
 
-        customerPortalExample.navigateTo();
-        customerPortalExample.customerSession();
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerSession();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(customerPortalExample);
+        assertCurrentUrlStartsWith(customerPortalExamplePage);
 
-        customerPortalExample.waitForCustomerSessionHeader();
+        customerPortalExamplePage.waitForCustomerSessionHeader();
         Assert.assertTrue(driver.getPageSource().contains("You visited this page"));
     }
 
     @Test
     public void productPortalListingTest() {
 
-        productPortalExample.navigateTo();
-        productPortalExample.productListing();
+        productPortalExamplePage.navigateTo();
+        productPortalExamplePage.productListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.waitForProductListingHeader();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.waitForProductListingHeader();
 
         Assert.assertTrue(driver.getPageSource().contains("iphone"));
         Assert.assertTrue(driver.getPageSource().contains("ipad"));
         Assert.assertTrue(driver.getPageSource().contains("ipod"));
 
-        productPortalExample.goToCustomers();
+        productPortalExamplePage.goToCustomers();
     }
 
     @Test
     public void goToProductPortalWithOneLoginTest() {
 
-        productPortalExample.navigateTo();
-        productPortalExample.productListing();
+        productPortalExamplePage.navigateTo();
+        productPortalExamplePage.productListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.waitForProductListingHeader();
-        productPortalExample.goToCustomers();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.waitForProductListingHeader();
+        productPortalExamplePage.goToCustomers();
 
-        assertCurrentUrlStartsWith(customerPortalExample);
-        customerPortalExample.customerListing();
-        customerPortalExample.goToProducts();
-        assertCurrentUrlStartsWith(productPortalExample);
+        assertCurrentUrlStartsWith(customerPortalExamplePage);
+        customerPortalExamplePage.customerListing();
+        customerPortalExamplePage.goToProducts();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
     }
 
     @Test
     public void logoutFromAllAppsTest() {
 
-        productPortalExample.navigateTo();
-        productPortalExample.productListing();
+        productPortalExamplePage.navigateTo();
+        productPortalExamplePage.productListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.waitForProductListingHeader();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.waitForProductListingHeader();
+
+        if (isRelative()) { //KEYCLOAK-1546
+            productPortalExamplePage.logOut();
+        } else {
+            driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + productPortalExamplePage);
+        }
 
-        productPortalExample.logOut();
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.productListing();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.productListing();
 
-        customerPortalExample.navigateTo();
-        customerPortalExample.customerListing();
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerListing();
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        customerPortalExample.logOut();
+        customerPortalExamplePage.logOut();
+    }
+
+    @Test
+    public void grantServerBasedApp() {
+        clientsPage.navigateTo();
+        loginPage.form().login(adminUser);
+
+        ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "customer-portal");
+        ClientRepresentation client = clientResource.toRepresentation();
+        client.setConsentRequired(true);
+        clientResource.update(client);
+
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        realm.setEventsEnabled(true);
+        realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN"));
+        testRealmResource().update(realm);
+
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerSession();
+
+        loginPage.form().login("bburke@redhat.com", "password");
+
+        assertTrue(oAuthGrantPage.isCurrent());
+
+        oAuthGrantPage.accept();
+
+        assertTrue(driver.getPageSource().contains("Your hostname:"));
+        assertTrue(driver.getPageSource().contains("You visited this page"));
+
+        applicationsPage.navigateTo();
+        applicationsPage.revokeGrantForApplication("customer-portal");
+
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerSession();
+
+        assertTrue(oAuthGrantPage.isCurrent());
+
+        loginEventsPage.navigateTo();
+        loginEventsPage.table().filter();
+        loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT");
+        loginEventsPage.table().update();
+
+        List<WebElement> resultList = loginEventsPage.table().rows();
+
+        assertEquals(2, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='customer-portal']"));
+
+        loginEventsPage.table().reset();
+        loginEventsPage.table().filterForm().addEventType("LOGIN");
+        loginEventsPage.table().update();
+        resultList = loginEventsPage.table().rows();
+
+        assertEquals(7, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
index 61d8efa..ab63862 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
@@ -1,26 +1,59 @@
 package org.keycloak.testsuite.adapter.example;
 
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
-import static org.junit.Assert.assertTrue;
 import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
 import org.keycloak.testsuite.adapter.page.JSConsoleExample;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Applications;
+import org.keycloak.testsuite.auth.page.login.OAuthGrant;
+import org.keycloak.testsuite.console.page.clients.ClientSettings;
+import org.keycloak.testsuite.console.page.clients.Clients;
+import org.keycloak.testsuite.console.page.events.Config;
+import org.keycloak.testsuite.console.page.events.LoginEvents;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.auth.page.AuthRealm.EXAMPLE;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 import static org.keycloak.testsuite.util.WaitUtils.pause;
 
 public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampleAdapterTest {
 
     @Page
-    private JSConsoleExample jsConsoleExample;
+    private JSConsoleExample jsConsoleExamplePage;
+
+    @Page
+    private Clients clientsPage;
+
+    @Page
+    private ClientSettings clientSettingsPage;
+
+    @Page
+    private Config configPage;
+
+    @Page
+    private LoginEvents loginEventsPage;
+
+    @Page
+    private OAuthGrant oAuthGrantPage;
+
+    @Page
+    private Applications applicationsPage;
 
     public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
 
@@ -34,7 +67,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
         RealmRepresentation jsConsoleRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/js-console/example-realm.json"));
 
         fixClientUrisUsingDeploymentUrl(jsConsoleRealm,
-                JSConsoleExample.CLIENT_ID, jsConsoleExample.buildUri().toASCIIString());
+                JSConsoleExample.CLIENT_ID, jsConsoleExamplePage.buildUri().toASCIIString());
 
         jsConsoleRealm.setAccessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY); // seconds
 
@@ -49,85 +82,157 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
 
     @Test
     public void testJSConsoleAuth() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
 
         pause(1000);
 
-        jsConsoleExample.logIn();
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "invalid-password");
-        assertCurrentUrlDoesntStartWith(jsConsoleExample);
+        assertCurrentUrlDoesntStartWith(jsConsoleExamplePage);
 
         testRealmLoginPage.form().login("invalid-user", "password");
-        assertCurrentUrlDoesntStartWith(jsConsoleExample);
+        assertCurrentUrlDoesntStartWith(jsConsoleExamplePage);
 
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Init Success (Authenticated)"));
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
         pause(1000);
 
-        jsConsoleExample.logOut();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.logOut();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Init Success (Not Authenticated)"));
     }
 
     @Test
     public void testRefreshToken() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
 
-        jsConsoleExample.refreshToken();
+        jsConsoleExamplePage.refreshToken();
         assertTrue(driver.getPageSource().contains("Failed to refresh token"));
 
-        jsConsoleExample.logIn();
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
-        jsConsoleExample.refreshToken();
+        jsConsoleExamplePage.refreshToken();
         assertTrue(driver.getPageSource().contains("Auth Refresh Success"));
     }
 
     @Test
     public void testRefreshTokenIfUnder30s() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
 
-        jsConsoleExample.refreshToken();
+        jsConsoleExamplePage.refreshToken();
         assertTrue(driver.getPageSource().contains("Failed to refresh token"));
 
-        jsConsoleExample.logIn();
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
-        jsConsoleExample.refreshTokenIfUnder30s();
+        jsConsoleExamplePage.refreshTokenIfUnder30s();
         assertTrue(driver.getPageSource().contains("Token not refreshed, valid for"));
 
         pause((TOKEN_LIFESPAN_LEEWAY + 2) * 1000);
 
-        jsConsoleExample.refreshTokenIfUnder30s();
+        jsConsoleExamplePage.refreshTokenIfUnder30s();
         assertTrue(driver.getPageSource().contains("Auth Refresh Success"));
     }
-    
-    @Test 
+
+    @Test
     public void testGetProfile() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
-        
-        jsConsoleExample.getProfile();
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
+
+        jsConsoleExamplePage.getProfile();
         assertTrue(driver.getPageSource().contains("Failed to load profile"));
-        
-        jsConsoleExample.logIn();
+
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
-        jsConsoleExample.getProfile();
+        jsConsoleExamplePage.getProfile();
         assertTrue(driver.getPageSource().contains("Failed to load profile"));
         assertTrue(driver.getPageSource().contains("\"username\": \"user\""));
     }
 
+    @Test
+    public void grantBrowserBasedApp() {
+        testRealmPage.setAuthRealm(EXAMPLE);
+        testRealmLoginPage.setAuthRealm(EXAMPLE);
+        clientsPage.setConsoleRealm(EXAMPLE);
+        configPage.setConsoleRealm(EXAMPLE);
+        loginEventsPage.setConsoleRealm(EXAMPLE);
+        applicationsPage.setAuthRealm(EXAMPLE);
+
+        jsConsoleExamplePage.navigateTo();
+        driver.manage().deleteAllCookies();
+
+        clientsPage.navigateTo();
+
+        loginPage.form().login("admin", "admin");
+
+        ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "js-console");
+        ClientRepresentation client = clientResource.toRepresentation();
+        client.setConsentRequired(true);
+        clientResource.update(client);
+
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        realm.setEventsEnabled(true);
+        realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN"));
+        testRealmResource().update(realm);
+
+        jsConsoleExamplePage.navigateTo();
+        jsConsoleExamplePage.logIn();
+
+        testRealmLoginPage.form().login("user", "password");
+
+        assertTrue(oAuthGrantPage.isCurrent());
+        oAuthGrantPage.accept();
+
+        assertTrue(driver.getPageSource().contains("Init Success (Authenticated)"));
+
+        applicationsPage.navigateTo();
+        applicationsPage.revokeGrantForApplication("js-console");
+
+        jsConsoleExamplePage.navigateTo();
+        jsConsoleExamplePage.logIn();
+
+        assertTrue(oAuthGrantPage.isCurrent());
+
+        loginEventsPage.navigateTo();
+        loginEventsPage.table().filter();
+        loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT");
+        loginEventsPage.table().update();
+
+        List<WebElement> resultList = loginEventsPage.table().rows();
+
+        assertEquals(2, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='js-console']"));
+
+        loginEventsPage.table().reset();
+        loginEventsPage.table().filterForm().addEventType("LOGIN");
+        loginEventsPage.table().update();
+        resultList = loginEventsPage.table().rows();
+
+        assertEquals(7, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='js-console']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='user']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index 9fb8165..8a470ad 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -34,7 +34,6 @@ import org.keycloak.testsuite.adapter.page.CustomerPortal;
 import org.keycloak.testsuite.adapter.page.InputPortal;
 import org.keycloak.testsuite.adapter.page.ProductPortal;
 import org.keycloak.testsuite.adapter.page.SecurePortal;
-import org.keycloak.testsuite.arquillian.jira.Jira;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
 import org.keycloak.util.BasicAuthHelper;
@@ -224,7 +223,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     }
 
     @Test
-    @Jira(value = "KEYCLOAK-1478") // rejected
     public void testLoginSSOIdleRemoveExpiredUserSessions() {
         // test login to customer-portal which does a bearer request to customer-db
         customerPortal.navigateTo();
@@ -279,7 +277,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         testRealmResource().update(demoRealmRep);
     }
 
-    @Jira("KEYCLOAK-518")
     @Test
     public void testNullBearerToken() {
         Client client = ClientBuilder.newClient();
@@ -293,7 +290,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         client.close();
     }
 
-    @Jira("KEYCLOAK-1368")
     @Test
     public void testNullBearerTokenCustomErrorPage() {
         Client client = ClientBuilder.newClient();
@@ -326,7 +322,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         client.close();
     }
 
-    @Jira("KEYCLOAK-518")
     @Test
     public void testBadUser() {
         Client client = ClientBuilder.newClient();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
index 79c0bef..cd969a3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
@@ -16,7 +16,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.adapter.page.SessionPortal;
-import org.keycloak.testsuite.arquillian.jira.Jira;
 import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
 import org.keycloak.testsuite.auth.page.account.Sessions;
 import org.keycloak.testsuite.auth.page.login.Login;
@@ -60,7 +59,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     @SecondBrowser
     protected WebDriver driver2;
 
-    @Jira("KEYCLOAK-732")
     @Test
     public void testSingleSessionInvalidated() {
 
@@ -102,7 +100,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     }
 
     @Test
-    @Jira("KEYCLOAK-741, KEYCLOAK-1485")
     public void testSessionInvalidatedAfterFailedRefresh() {
         RealmRepresentation testRealmRep = testRealmResource().toRepresentation();
         ClientResource sessionPortalRes = null;
@@ -139,7 +136,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     }
 
     @Test
-    @Jira("KEYCLOAK-942")
     public void testAdminApplicationLogout() {
         // login as bburke
         loginAndCheckSession(driver, testRealmLoginPage);
@@ -157,7 +153,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     }
 
     @Test
-    @Jira("KEYCLOAK-1216, KEYCLOAK-1485")
     public void testAccountManagementSessionsLogout() {
         // login as bburke
         loginAndCheckSession(driver, testRealmLoginPage);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/BindingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/BindingsTest.java
new file mode 100644
index 0000000..a89eac6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/BindingsTest.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.authentication;
+
+import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.bindings.Bindings;
+import org.keycloak.testsuite.console.page.authentication.bindings.BindingsForm.BindingsOption;
+import org.keycloak.testsuite.console.page.authentication.bindings.BindingsForm.BindingsSelect;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class BindingsTest extends AbstractConsoleTest {
+    
+    @Page
+    private Bindings bindingsPage;
+    
+    @Before
+    public void beforeBindingsTest() {
+        bindingsPage.navigateTo();
+    }
+    
+    @Test
+    public void bindingsTest() {
+        bindingsPage.form().select(BindingsSelect.BROWSER, BindingsOption.REGISTRATION);
+        bindingsPage.form().select(BindingsSelect.REGISTRATION, BindingsOption.RESET_CREDENTIALS);
+        bindingsPage.form().select(BindingsSelect.DIRECT_GRANT, BindingsOption.BROWSER);
+        bindingsPage.form().select(BindingsSelect.RESET_CREDENTIALS, BindingsOption.DIRECT_GRANT);
+        bindingsPage.form().save();
+        
+        assertEquals("Success! Your changes have been saved to the realm.", bindingsPage.getSuccessMessage());
+        
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        
+        assertEquals("registration", realm.getBrowserFlow());
+        assertEquals("reset credentials", realm.getRegistrationFlow());
+        assertEquals("browser", realm.getDirectGrantFlow());
+        assertEquals("direct grant", realm.getResetCredentialsFlow());
+        assertEquals("clients", realm.getClientAuthenticationFlow());
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
new file mode 100644
index 0000000..72455e2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
@@ -0,0 +1,109 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.authentication;
+
+import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicy;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.Digits;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.OTPHashAlg;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.OTPType;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class OTPPolicyTest extends AbstractConsoleTest {
+    
+    @Page
+    private OTPPolicy otpPolicyPage;
+    
+    @Before
+    public void beforeOTPPolicyTest() {
+        otpPolicyPage.navigateTo();
+    }
+    
+    @Test
+    public void otpPolicyTest() {
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA256, Digits.EIGHT, "10", "50");
+        assertEquals("Success! Your changes have been saved to the realm.", otpPolicyPage.getSuccessMessage());
+        
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        assertEquals("hotp", realm.getOtpPolicyType());
+        assertEquals("HmacSHA256", realm.getOtpPolicyAlgorithm());
+        assertEquals(Integer.valueOf(8), realm.getOtpPolicyDigits());
+        assertEquals(Integer.valueOf(10), realm.getOtpPolicyLookAheadWindow());
+        assertEquals(Integer.valueOf(50), realm.getOtpPolicyInitialCounter());
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA512, Digits.EIGHT, "10", "40");
+        assertEquals("Success! Your changes have been saved to the realm.", otpPolicyPage.getSuccessMessage());
+        
+        realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(40), realm.getOtpPolicyPeriod());
+    }      
+    
+    @Test
+    public void invalidValuesTest() {
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "", "30");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, " ", "30");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "no number", "30");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(1), realm.getOtpPolicyLookAheadWindow());
+
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+
+        realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(30), realm.getOtpPolicyPeriod());
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "1 2");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        
+        realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(0), realm.getOtpPolicyInitialCounter());
+    }
+}