keycloak-aplcache

Changes

Details

diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 8609f07..eff01c4 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -18,6 +18,7 @@
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
+        <class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ClaimTypeEntity</class>
         <class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
 
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
index 6139c3d..b27e9bd 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
@@ -73,13 +73,14 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
-        <createTable tableName="CLIENT_ALLOWED_IDENTITY_PROVIDER">
+        <createTable tableName="CLIENT_IDENTITY_PROVIDER_MAPPING">
             <column name="CLIENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="INTERNAL_ID" type="VARCHAR(36)">
+            <column name="IDENTITY_PROVIDER_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
+            <column name="RETRIEVE_TOKEN" type="BOOLEAN(1)"/>
         </createTable>
         <createTable tableName="CLIENT_PROTOCOL_MAPPER">
             <column name="CLIENT_ID" type="VARCHAR(36)">
@@ -104,10 +105,11 @@
         <addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
         <addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
         <addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
-        <addForeignKeyConstraint baseColumnNames="INTERNAL_ID" baseTableName="CLIENT_ALLOWED_IDENTITY_PROVIDER" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
-        <addUniqueConstraint columnNames="INTERNAL_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_ALLOWED_IDENTITY_PROVIDER"/>
+        <addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
+        <addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
         <addForeignKeyConstraint baseColumnNames="MAPPING_ID" baseTableName="CLIENT_PROTOCOL_MAPPER" constraintName="FK_CPCM" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
         <addUniqueConstraint columnNames="CLIENT_ID,MAPPING_ID" constraintName="UK_CPCM" tableName="CLIENT_PROTOCOL_MAPPER"/>
         <addUniqueConstraint columnNames="PROVIDER_NONIMAL_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
+        <addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROVIDER_MAPPING"/>
     </changeSet>
 </databaseChangeLog>
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 9ca74f0..6537fc2 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -2,7 +2,6 @@ package org.keycloak.representations.idm;
 
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -29,7 +28,7 @@ public class ApplicationRepresentation {
     protected Boolean fullScopeAllowed;
     protected Integer nodeReRegistrationTimeout;
     protected Map<String, Integer> registeredNodes;
-    protected List<String> allowedIdentityProviders;
+    protected List<ClientIdentityProviderMappingRepresentation> identityProviders;
     protected List<ClientProtocolMappingRepresentation> protocolMappers;
 
     public String getId() {
@@ -192,12 +191,12 @@ public class ApplicationRepresentation {
         this.frontchannelLogout = frontchannelLogout;
     }
 
-    public List<String> getAllowedIdentityProviders() {
-        return this.allowedIdentityProviders;
+    public List<ClientIdentityProviderMappingRepresentation> getIdentityProviders() {
+        return this.identityProviders;
     }
 
-    public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
-        this.allowedIdentityProviders = allowedIdentityProviders;
+    public void setIdentityProviders(List<ClientIdentityProviderMappingRepresentation> identityProviders) {
+        this.identityProviders = identityProviders;
     }
 
     public List<ClientProtocolMappingRepresentation> getProtocolMappers() {
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java
new file mode 100644
index 0000000..fdc02d3
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.representations.idm;
+
+/**
+ * @author pedroigor
+ */
+public class ClientIdentityProviderMappingRepresentation {
+
+    protected String id;
+    protected boolean retrieveToken;
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String identityProviderId) {
+        this.id = identityProviderId;
+    }
+
+    public boolean isRetrieveToken() {
+        return this.retrieveToken;
+    }
+
+    public void setRetrieveToken(boolean retrieveToken) {
+        this.retrieveToken = retrieveToken;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
index aa095a7..c12a9eb 100755
--- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
@@ -2,7 +2,6 @@ package org.keycloak.representations.idm;
 
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -23,8 +22,8 @@ public class OAuthClientRepresentation {
     protected Boolean directGrantsOnly;
     protected Boolean fullScopeAllowed;
     protected Boolean frontchannelLogout;
-    protected List<String> allowedIdentityProviders;
     protected List<ClientProtocolMappingRepresentation> protocolMappers;
+    private List<ClientIdentityProviderMappingRepresentation> identityProviders;
 
 
     public String getId() {
@@ -139,12 +138,12 @@ public class OAuthClientRepresentation {
         this.frontchannelLogout = frontchannelLogout;
     }
 
-    public List<String> getAllowedIdentityProviders() {
-        return this.allowedIdentityProviders;
+    public List<ClientIdentityProviderMappingRepresentation> getIdentityProviders() {
+        return this.identityProviders;
     }
 
-    public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
-        this.allowedIdentityProviders = allowedIdentityProviders;
+    public void setIdentityProviders(List<ClientIdentityProviderMappingRepresentation> identityProviders) {
+        this.identityProviders = identityProviders;
     }
 
     public List<ClientProtocolMappingRepresentation> getProtocolMappers() {
diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index a2fbacb..6bbd301 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -32,6 +32,7 @@
                 <!ENTITY Events SYSTEM "modules/events.xml">
                 <!ENTITY AdminApi SYSTEM "modules/admin-rest-api.xml">
                 <!ENTITY UserFederation SYSTEM "modules/user-federation.xml">
+                <!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
                 <!ENTITY ExportImport SYSTEM "modules/export-import.xml">
                 <!ENTITY ServerCache SYSTEM "modules/cache.xml">
                 <!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
@@ -119,6 +120,7 @@ This one is short
     &AdminApi;
     &Events;
     &UserFederation;
+    &Kerberos;
     &ExportImport;
     &ServerCache;
     &SAML;
diff --git a/docbook/reference/en/en-US/modules/identity-broker.xml b/docbook/reference/en/en-US/modules/identity-broker.xml
index 6ccc630..233afed 100755
--- a/docbook/reference/en/en-US/modules/identity-broker.xml
+++ b/docbook/reference/en/en-US/modules/identity-broker.xml
@@ -962,7 +962,7 @@ Authorization: Bearer {keycloak_access_token}]]></programlisting>
     </section>
 
     <section>
-        <title>Enabling/Disabling Identity Providers for Service Providers</title>
+        <title>Configuring Identity Providers for Applications</title>
         <para>
             By default, all identity providers enabled for a particular realm are also available to all its applications.
             However, you can also specify which identity providers should be available when
@@ -993,6 +993,10 @@ Authorization: Bearer {keycloak_access_token}]]></programlisting>
                 </para>
             </listitem>
         </orderedlist>
+        <para>
+            From this page you can also configure if an application is allowed to retrieve tokens from an specific identity provider. For that,
+            just click on the <emphasis>Can Retrieve Token</emphasis> button.
+        </para>
     </section>
 
     <section>
diff --git a/docbook/reference/en/en-US/modules/kerberos.xml b/docbook/reference/en/en-US/modules/kerberos.xml
new file mode 100644
index 0000000..24b5d9b
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/kerberos.xml
@@ -0,0 +1,231 @@
+<chapter id="kerberos">
+    <title>Kerberos brokering</title>
+    <para>
+        Keycloak supports login with Kerberos ticket through SPNEGO. SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) is used
+        to authenticate transparently through the web browser after the user has been authenticated when logging-in his session.
+        For non-web cases or when ticket is not available during login, Keycloak also supports login with Kerberos username/password.
+    </para>
+    <para>
+        A typical use case for web authentication is the following:
+        <orderedlist>
+            <listitem>
+                <para>
+                    User logs into his desktop (Such as a Windows machine in Active Directory domain or Linux machine with Kerberos integration enabled).
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    User then uses his browser (IE/Firefox/Chrome) to access a web application secured by Keycloak.
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Application redirects to Keycloak login.
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Keycloak sends HTML login screen together with status 401 and HTTP header <literal>WWW-Authenticate: Negotiate</literal>
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    In case that browser has Kerberos ticket from desktop login, it transfers the desktop sign on information to the
+                    Keycloak in header <literal>Authorization: Negotiate 'spnego-token'</literal> . Otherwise it just displays login screen.
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Keycloak validates token from browser and authenticate user. It provisions user data from LDAP (in case of
+                    LDAPFederationProvider with Kerberos authentication support) or let user to update his profile and prefill data
+                    (in case of KerberosFederationProvider).
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Keycloak returns back to the application. Communication between Keycloak and application happens through OpenID
+                    Connect or SAML messages. The fact that Keycloak was authenticated through Kerberos is hidden from the application.
+                    So Keycloak acts as broker to Kerberos/SPNEGO login.
+                </para>
+            </listitem>
+        </orderedlist>
+    </para>
+    <para>
+        For setup there are 3 main parts:
+        <orderedlist>
+            <listitem>
+                <para>
+                    Setup and configuration of Kerberos server (KDC)
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Setup and configuration of Keycloak server
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Setup and configuration of client machines
+                </para>
+            </listitem>
+        </orderedlist>
+    </para>
+    <section>
+        <title>Setup of Kerberos server</title>
+        <para>
+            This is platform dependent. Exact steps depend on your OS and the Kerberos vendor you're going to use.
+            Consult Windows Active Directory, MIT Kerberos and your OS documentation for how exactly to setup and configure Kerberos server.
+        </para>
+        <para>
+            At least you will need to:
+            <itemizedlist>
+                <listitem>
+                    <para>
+                        Add some user principals to your Kerberos database. You can also integrate your Kerberos with LDAP,
+                        which means that user accounts will be provisioned from LDAP server.
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        Add service principal for "HTTP" service. For example if your Keycloak server will be running on
+                        <literal>www.mydomain.org</literal> you may need to add principal <literal>HTTP/www.mydomain.org@MYDOMAIN.ORG</literal>
+                        assuming that MYDOMAIN.ORG will be your Kerberos realm.
+                    </para>
+                    <para>
+                        For example on MIT Kerberos you can run "kadmin" session. If you are on same machine where is MIT Kerberos, you can simply use command:
+                        <programlisting><![CDATA[
+sudo kadmin.local
+]]></programlisting>
+                        Then add HTTP principal and export his key to keytab file with the commands like:
+                        <programlisting><![CDATA[
+addprinc -randkey HTTP/www.mydomain.org@MYDOMAIN.ORG
+ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
+]]></programlisting>
+                        Keytab file <literal>/tmp/http.keytab</literal> will need to be accessible on the host where keycloak server will be running.
+                    </para>
+                </listitem>
+            </itemizedlist>
+        </para>
+    </section>
+    <section>
+        <title>Setup and configuration of Keycloak server</title>
+        <itemizedlist>
+            <listitem>
+                <para>
+                    Install kerberos client. This is again platform dependent. If you are on Fedora, Ubuntu or RHEL, you can install package <literal>freeipa-client</literal>,
+                    which contains Kerberos client and bunch of other stuff.
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Configure kerberos client (on linux it's in file <literal>/etc/krb5.conf</literal> ). You need to put your Kerberos realm and at least
+                    configure the Http domains your server will be running on. For the example realm MYDOMAIN.ORG you may configure <literal>domain_realm</literal> section like this:
+                    <programlisting><![CDATA[
+[domain_realm]
+  .mydomain.org = MYDOMAIN.ORG
+  mydomain.org = MYDOMAIN.ORG
+]]></programlisting>
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Export keytab file with HTTP principal and make sure the file is accessible to the process under which Keycloak
+                    server is running. For production, it's ideal if it's readable just by this process and not by someone else.
+                    For MIT Kerberos example above, we already exported keytab to <literal>/tmp/http.keytab</literal> . If your KDC and Keycloak
+                    are running on same host, you have file already available.
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Finally run Keycloak server and configure SPNEGO/Kerberos authentication in Keycloak admin console. Keycloak supports Kerberos authentication
+                    through <link linkend='user_federation'>Federation provider SPI</link> . We have 2 federation providers with Kerberos authentication support:
+                    <variablelist>
+                        <varlistentry>
+                            <term>Kerberos</term>
+                            <listitem>
+                                <para>
+                                    This provider is useful if you want to authenticate with Kerberos <literal>NOT</literal> backed by LDAP server.
+                                    In this case, users are usually created to Keycloak database after first successful SPNEGO/Kerberos login
+                                    and they may need to update profile after first login, as Kerberos protocol itself doesn't provision
+                                    any data like first name, last name or email.
+                                </para>
+                                <para>
+                                    You can also choose if users can authenticate with classic username/password. In this case, if user doesn't have SPNEGO ticket available,
+                                    Keycloak will display login screen and user can fill his Kerberos username and password on login screen. Username/password works also for non-web flows like
+                                    <link linkend='direct-access-grants'>Direct Access grants</link>.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>LDAP</term>
+                            <listitem>
+                                <para>
+                                    This provider is useful if you want to authenticate with Kerberos backed by LDAP server.
+                                    In this case, data about users are provisioned from LDAP server after successful Kerberos authentication.
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
+                </para>
+            </listitem>
+        </itemizedlist>
+    </section>
+    <section>
+        <title>Setup and configuration of client machines</title>
+        <para>
+            Clients need to install kerberos client and setup krb5.conf as described above. Additionally they need to enable SPNEGO login support in their browser.
+            See for example <ulink url="http://www.microhowto.info/howto/configure_firefox_to_authenticate_using_spnego_and_kerberos.html">this</ulink>
+            for more info about Firefox. URI <literal>.mydomain.org</literal> must be allowed in <literal>network.negotiate-auth.trusted-uris</literal> config option.
+        </para>
+        <para>
+            In windows domain, clients usually don't need to configure anything special as IE is already able to participate in SPNEGO authentication for the windows domain.
+        </para>
+    </section>
+    <section>
+        <title>Example setups</title>
+        <para>
+            For easier testing with Kerberos, we provided some example setups to test.
+        </para>
+        <section>
+            <title>Keycloak and FreeIPA docker image</title>
+            <para>
+                Once you install <ulink url="https://www.docker.com/">docker</ulink>, you can run docker image with <ulink url="http://www.freeipa.org/">FreeIPA</ulink>
+                server installed. FreeIPA provides integrated security solution with MIT Kerberos and 389 LDAP server among other things . The image provides
+                also Keycloak server configured with LDAP Federation provider and enabled SPNEGO/Kerberos authentication against the FreeIPA server.
+                See details <ulink url="https://github.com/mposolda/keycloak-freeipa-docker/blob/master/README.md">here</ulink> .
+            </para>
+        </section>
+        <section>
+            <title>ApacheDS testing Kerberos server</title>
+            <para>
+                For quick testing and unit tests, we use very simple <ulink url="http://directory.apache.org/apacheds/">ApacheDS</ulink> Kerberos server.
+                You need to build Keycloak from sources and then run Kerberos server with maven-exec-plugin from our testsuite. See details
+                <ulink url="https://github.com/keycloak/keycloak/blob/master/testsuite/integration/README.md#kerberos-server">here</ulink> .
+            </para>
+        </section>
+    </section>
+    <section>
+        <title>Troubleshooting</title>
+        <para>
+            If you have issues, we recommend to enable more logging by:
+            <itemizedlist>
+                <listitem>
+                    <para>
+                        Enable <literal>Debug</literal> flag in admin console for Kerberos or LDAP federation providers
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        Enable TRACE logging for category <literal>org.keycloak</literal> in logging section of <literal>$WILDFLY_HOME/standalone/configuration/standalone.xml</literal>
+                        to receive more info <literal>$WILDFLY_HOME/standalone/log/server.log</literal>
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        Add system properties <literal>-Dsun.security.krb5.debug=true</literal> and <literal>-Dsun.security.spnego.debug=true</literal>
+                    </para>
+                </listitem>
+            </itemizedlist>
+        </para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/user-federation.xml b/docbook/reference/en/en-US/modules/user-federation.xml
index 777f4b7..a8e6c17 100755
--- a/docbook/reference/en/en-US/modules/user-federation.xml
+++ b/docbook/reference/en/en-US/modules/user-federation.xml
@@ -98,6 +98,14 @@
                         </listitem>
                     </varlistentry>
                     <varlistentry>
+                        <term>Allow Kerberos authentication</term>
+                        <listitem>
+                            <para>
+                                Enable Kerberos/SPNEGO authentication in realm with users data provisioned from LDAP. More info in <link linkend="kerberos">Kerberos section</link>.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
                         <term>Other options</term>
                         <listitem>
                             <para>
diff --git a/examples/broker/facebook-authentication/facebook-identity-provider-realm.json b/examples/broker/facebook-authentication/facebook-identity-provider-realm.json
index 0baa717..5243879 100644
--- a/examples/broker/facebook-authentication/facebook-identity-provider-realm.json
+++ b/examples/broker/facebook-authentication/facebook-identity-provider-realm.json
@@ -39,6 +39,9 @@
             "baseUrl": "/facebook-authentication",
             "redirectUris": [
               "/facebook-authentication/*"
+            ],
+            "webOrigins": [
+              "http://localhost:8080"
             ]
         }
     ],
diff --git a/examples/broker/google-authentication/google-identity-provider-realm.json b/examples/broker/google-authentication/google-identity-provider-realm.json
index 751bbf0..22df7e4 100644
--- a/examples/broker/google-authentication/google-identity-provider-realm.json
+++ b/examples/broker/google-authentication/google-identity-provider-realm.json
@@ -39,6 +39,9 @@
             "baseUrl": "/google-authentication",
             "redirectUris": [
               "/google-authentication/*"
+            ],
+            "webOrigins": [
+              "http://localhost:8080"
             ]
         }
     ],
diff --git a/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json b/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json
index 5433cf0..3369daf 100644
--- a/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json
+++ b/examples/broker/saml-broker-authentication/saml-broker-authentication-realm.json
@@ -38,8 +38,7 @@
             "adminUrl": "/saml-broker-authentication",
             "baseUrl": "/saml-broker-authentication",
             "redirectUris": [
-              "/saml-broker-authentication/*",
-              "http://localhost:8080/saml-broker-authentication/*"
+              "/saml-broker-authentication/*"
             ],
             "webOrigins": [
               "http://localhost:8080"
@@ -55,7 +54,7 @@
           "updateProfileFirstLogin" : "true",
           "storeToken" : "true",
           "config": {
-              "singleSignOnServiceUrl": "http://localhost:8081/auth/realms/saml-broker-realm/protocol/saml",
+              "singleSignOnServiceUrl": "http://localhost:8080/auth/realms/saml-broker-realm/protocol/saml",
               "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
               "signingCertificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin",
               "wantAuthnRequestsSigned": true,
diff --git a/examples/broker/saml-broker-authentication/saml-broker-realm.json b/examples/broker/saml-broker-authentication/saml-broker-realm.json
index 0fc0643..016b843 100644
--- a/examples/broker/saml-broker-authentication/saml-broker-realm.json
+++ b/examples/broker/saml-broker-authentication/saml-broker-realm.json
@@ -28,10 +28,10 @@
     },
     "applications": [
         {
-            "name": "http://localhost:8081/auth/",
+            "name": "http://localhost:8080/auth/",
             "enabled": true,
             "redirectUris": [
-              "http://localhost:8081/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider"
+              "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider"
             ],
           "attributes": {
             "saml.assertion.signature": "true",
diff --git a/examples/broker/saml-broker-authentication/src/main/webapp/keycloak.json b/examples/broker/saml-broker-authentication/src/main/webapp/keycloak.json
index dccd4a3..5c86ef0 100644
--- a/examples/broker/saml-broker-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/saml-broker-authentication/src/main/webapp/keycloak.json
@@ -2,7 +2,7 @@
   "realm" : "saml-broker-authentication-realm",
   "resource" : "saml-broker-authentication",
   "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
-  "auth-server-url": "http://localhost:8081/auth",
+  "auth-server-url": "/auth",
   "ssl-required" : "external",
   "public-client" : true
 }
diff --git a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
new file mode 100644
index 0000000..7243636
--- /dev/null
+++ b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
@@ -0,0 +1,8 @@
+{
+  "realm" : "twitter-identity-provider-realm",
+  "resource" : "twitter-authentication",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url": "/auth",
+  "ssl-required" : "external",
+  "public-client" : true
+}
diff --git a/examples/broker/twitter-authentication/twitter-identity-provider-realm.json b/examples/broker/twitter-authentication/twitter-identity-provider-realm.json
index 10c6b55..90aef3f 100644
--- a/examples/broker/twitter-authentication/twitter-identity-provider-realm.json
+++ b/examples/broker/twitter-authentication/twitter-identity-provider-realm.json
@@ -40,6 +40,9 @@
             "baseUrl": "/twitter-authentication",
             "redirectUris": [
                 "/twitter-authentication/*"
+            ],
+            "webOrigins": [
+              "http://localhost:8080"
             ]
         },
         {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index 47c3fca..59f169f 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -171,7 +171,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             controller : 'RealmIdentityProviderCtrl'
         })
         .when('/realms/:realm/identity-provider-settings/provider/:provider_id/:id', {
-            templateUrl : function(params){ return 'partials/realm-identity-provider-' + params.provider_id + '.html'; },
+            templateUrl : function(params){ return resourceUrl + '/partials/realm-identity-provider-' + params.provider_id + '.html'; },
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
index 569c866..b412288 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
@@ -49,75 +49,95 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real
     });
 });
 
-module.controller('ApplicationIdentityProviderCtrl', function($scope, $location, realm, application, Application, $location, Notifications) {
+module.controller('ApplicationIdentityProviderCtrl', function($scope, $location, $route, realm, application, Application, $location, Notifications) {
     $scope.realm = realm;
     $scope.application = angular.copy(application);
+    var length = 0;
 
-    $scope.identityProviders = [];
+    if ($scope.application.identityProviders) {
+        length = $scope.application.identityProviders.length;
+    } else {
+        $scope.application.identityProviders = new Array(realm.identityProviders.length);
+    }
 
-    if (!$scope.application.allowedIdentityProviders) {
-        $scope.application.allowedIdentityProviders = [];
+    for (j = length; j < realm.identityProviders.length; j++) {
+        $scope.application.identityProviders[j] = {};
     }
 
+    $scope.identityProviders = [];
+
     for (j = 0; j < realm.identityProviders.length; j++) {
         var identityProvider = realm.identityProviders[j];
         var match = false;
+        var applicationProvider;
+
+        for (i = 0; i < $scope.application.identityProviders.length; i++) {
+            applicationProvider = $scope.application.identityProviders[i];
 
-        for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) {
-            var appProvider = $scope.application.allowedIdentityProviders[i];
+            if (applicationProvider) {
+                if (applicationProvider.retrieveToken) {
+                    applicationProvider.retrieveToken = applicationProvider.retrieveToken.toString();
+                } else {
+                    applicationProvider.retrieveToken = false.toString();
+                }
+
+                if (applicationProvider.id == identityProvider.id) {
+                    $scope.identityProviders[i] = {};
+                    $scope.identityProviders[i].identityProvider = identityProvider;
+                    $scope.identityProviders[i].retrieveToken = applicationProvider.retrieveToken.toString();
+                    break;
+                }
 
-            if (appProvider == identityProvider.id) {
-                $scope.identityProviders[i] = identityProvider;
-                match = true;
+                applicationProvider = null;
             }
         }
 
-        if (!match) {
-            var length = $scope.identityProviders.length;
+        if (applicationProvider == null) {
+            var length = $scope.identityProviders.length + $scope.application.identityProviders.length;
 
-            length = length + $scope.application.allowedIdentityProviders.length;
-
-            $scope.identityProviders[length] = identityProvider;
+            $scope.identityProviders[length] = {};
+            $scope.identityProviders[length].identityProvider = identityProvider;
+            $scope.identityProviders[length].retrieveToken = false.toString();
         }
     }
 
     $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined });
 
+    var oldCopy = angular.copy($scope.application);
+
     $scope.save = function() {
         var selectedProviders = [];
 
-        for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) {
-            var appProvider = $scope.application.allowedIdentityProviders[i];
+        for (i = 0; i < $scope.application.identityProviders.length; i++) {
+            var appProvider = $scope.application.identityProviders[i];
 
-            if (appProvider) {
+            if (appProvider.id != null && appProvider.id != false) {
                 selectedProviders[selectedProviders.length] = appProvider;
             }
         }
 
-        $scope.allowedIdentityProviders = $scope.application.allowedIdentityProviders;
-        $scope.application.allowedIdentityProviders = selectedProviders;
+        $scope.application.identityProviders = selectedProviders;
 
         Application.update({
             realm : realm.realm,
             application : application.id
         }, $scope.application, function() {
             $scope.changed = false;
-            $scope.application.allowedIdentityProviders = $scope.allowedIdentityProviders;
-            $location.url("/realms/" + realm.realm + "/applications/" + application.id + "/identity-provider");
+            $route.reload();
             Notifications.success("Your changes have been saved to the application.");
         });
     };
 
     $scope.reset = function() {
-        $scope.application = angular.copy(application);
+        $scope.application = angular.copy(oldCopy);
         $scope.changed = false;
     };
 
-    $scope.$watch(function() {
-        return $location.path();
-    }, function() {
-        $scope.path = $location.path().substring(1).split("/");
-    });
+    $scope.$watch('application', function() {
+        if (!angular.equals($scope.application, oldCopy)) {
+            $scope.changed = true;
+        }
+    }, true);
 });
 
 module.controller('ApplicationSamlKeyCtrl', function($scope, $location, $http, $upload, realm, application,
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
index dec42f5..1237f57 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
@@ -324,74 +324,94 @@ module.controller('OAuthClientRevocationCtrl', function($scope, realm, oauth, OA
     }
 });
 
-module.controller('OAuthClientIdentityProviderCtrl', function($scope, realm, oauth, OAuthClient, $location, Notifications) {
+module.controller('OAuthClientIdentityProviderCtrl', function($scope, $route, realm, oauth, OAuthClient, $location, Notifications) {
     $scope.realm = realm;
     $scope.oauth = angular.copy(oauth);
+    var length = 0;
 
-    $scope.identityProviders = [];
+    if ($scope.oauth.identityProviders) {
+        length = $scope.oauth.identityProviders.length;
+    } else {
+        $scope.oauth.identityProviders = new Array(realm.identityProviders.length);
+    }
 
-    if (!$scope.oauth.allowedIdentityProviders) {
-        $scope.oauth.allowedIdentityProviders = [];
+    for (j = length; j < realm.identityProviders.length; j++) {
+        $scope.oauth.identityProviders[j] = {};
     }
 
+    $scope.identityProviders = [];
+
     for (j = 0; j < realm.identityProviders.length; j++) {
         var identityProvider = realm.identityProviders[j];
         var match = false;
-
-        for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) {
-            var appProvider = $scope.oauth.allowedIdentityProviders[i];
-
-            if (appProvider == identityProvider.id) {
-                $scope.identityProviders[i] = identityProvider;
-                match = true;
+        var applicationProvider;
+
+        for (i = 0; i < $scope.oauth.identityProviders.length; i++) {
+            applicationProvider = $scope.oauth.identityProviders[i];
+
+            if (applicationProvider) {
+                if (applicationProvider.retrieveToken) {
+                    applicationProvider.retrieveToken = applicationProvider.retrieveToken.toString();
+                } else {
+                    applicationProvider.retrieveToken = false.toString();
+                }
+
+                if (applicationProvider.id == identityProvider.id) {
+                    $scope.identityProviders[i] = {};
+                    $scope.identityProviders[i].identityProvider = identityProvider;
+                    $scope.identityProviders[i].retrieveToken = applicationProvider.retrieveToken.toString();
+                    break;
+                }
+
+                applicationProvider = null;
             }
         }
 
-        if (!match) {
-            var length = $scope.identityProviders.length;
-
-            length = length + $scope.oauth.allowedIdentityProviders.length;
+        if (applicationProvider == null) {
+            var length = $scope.identityProviders.length + $scope.oauth.identityProviders.length;
 
-            $scope.identityProviders[length] = identityProvider;
+            $scope.identityProviders[length] = {};
+            $scope.identityProviders[length].identityProvider = identityProvider;
+            $scope.identityProviders[length].retrieveToken = false.toString();
         }
     }
 
     $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined });
 
+    var oldCopy = angular.copy($scope.oauth);
+
     $scope.save = function() {
         var selectedProviders = [];
 
-        for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) {
-            var appProvider = $scope.oauth.allowedIdentityProviders[i];
+        for (i = 0; i < $scope.oauth.identityProviders.length; i++) {
+            var appProvider = $scope.oauth.identityProviders[i];
 
-            if (appProvider) {
+            if (appProvider.id != null && appProvider.id != false) {
                 selectedProviders[selectedProviders.length] = appProvider;
             }
         }
 
-        $scope.allowedIdentityProviders = $scope.oauth.allowedIdentityProviders;
-        $scope.oauth.allowedIdentityProviders = selectedProviders;
+        $scope.oauth.identityProviders = selectedProviders;
 
         OAuthClient.update({
             realm : realm.realm,
             oauth : oauth.id
         }, $scope.oauth, function() {
             $scope.changed = false;
-            $scope.oauth.allowedIdentityProviders = $scope.allowedIdentityProviders;
-            $location.url("/realms/" + realm.realm + "/oauth-clients/" + oauth.id + "/identity-provider");
+            $route.reload();
             Notifications.success("Your changes have been saved to the application.");
         });
     };
 
     $scope.reset = function() {
-        $scope.oauth = angular.copy(oauth);
+        $scope.oauth = angular.copy(oldCopy);
         $scope.changed = false;
     };
 
-    $scope.$watch(function() {
-        return $location.path();
-    }, function() {
-        $scope.path = $location.path().substring(1).split("/");
-    });
+    $scope.$watch('oauth', function() {
+        if (!angular.equals($scope.oauth, oldCopy)) {
+            $scope.changed = true;
+        }
+    }, true);
 });
 
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html
index 57adcd3..aeb3b8d 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html
@@ -7,17 +7,24 @@
             <li><a href="#/realms/{{realm.realm}}/applications/{{application.id}}">{{application.name}}</a></li>
             <li class="active">Identity Provider</li>
         </ol>
-        <h2 data-ng-hide="create"><span>{{application.name}}</span> Identity Provider Settings<span tooltip-placement="right" tooltip="Select which identity providers can be used for this application. By default, all identity providers are enabled. Select one or more to restrict." class="fa fa-info-circle"></span></h2>
+        <h2 data-ng-hide="create"><span>{{application.name}}</span> Identity Provider Settings</h2>
         <form class="form-horizontal" name="identityProviderForm" novalidate>
             <div class="form-group" ng-repeat="identityProvider in identityProviders">
-                <legend><span class="text">{{identityProvider.name}}</span></legend>
-                <label class="col-sm-2 control-label" for="{{identityProvider.id}}">Enable</label>
+                <legend><span class="text">{{identityProvider.identityProvider.name}}</span></legend>
+                <label class="col-sm-2 control-label" for="{{identityProvider.identityProvider.id}}">Enable&nbsp;<span tooltip-placement="right" tooltip="If disabled, users can not login to the application using this identity provider." class="fa fa-info-circle"></span></label>
                 <div class="col-sm-4">
-                    <input ng-model="application.allowedIdentityProviders[$index]" name="identityProvider.id" id="identityProvider.id" value="identityProvider.id" onoffswitchmodel />
+                    <input ng-model="application.identityProviders[$index].id" name="identityProvider.identityProvider.id" id="identityProvider.identityProvider.id" value="identityProvider.identityProvider.id" onoffswitchmodel />
+                </div>
+                <div data-ng-show="application.identityProviders[$index].id">
+                    <label class="col-sm-2 control-label" for="{{identityProvider.identityProvider.id}}retrieveToken">Can Retrieve Token&nbsp;<span tooltip-placement="right" tooltip="If disabled, the application can not retrieve tokens from the identity provider." class="fa fa-info-circle"></span></label>
+                    <div class="col-sm-4">
+                        <input ng-model="application.identityProviders[$index].retrieveToken" name="identityProvider.identityProvider.id + 'retrieveToken'" id="identityProvider.identityProvider.id + 'retrieveToken'" value="true" onoffswitchmodel />
+                    </div>
                 </div>
             </div>
             <div class="pull-right form-actions">
-                <button kc-save>Save</button>
+                <button kc-reset data-ng-show="changed">Clear changes</button>
+                <button kc-save data-ng-show="changed">Save</button>
             </div>
         </form>
     </div>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html
index 92f3e87..619ce33 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html
@@ -10,14 +10,21 @@
         <h2 data-ng-hide="create"><span>{{oauth.name}}</span> Identity Provider Settings</h2>
         <form class="form-horizontal" name="identityProviderForm" novalidate>
             <div class="form-group" ng-repeat="identityProvider in identityProviders">
-                <legend><span class="text">{{identityProvider.name}}</span></legend>
-                <label class="col-sm-2 control-label" for="{{identityProvider.id}}">Enable</label>
+                <legend><span class="text">{{identityProvider.identityProvider.name}}</span></legend>
+                <label class="col-sm-2 control-label" for="{{identityProvider.identityProvider.id}}">Enable&nbsp;<span tooltip-placement="right" tooltip="If disabled, users can not login to the application using this identity provider." class="fa fa-info-circle"></span></label>
                 <div class="col-sm-4">
-                    <input ng-model="oauth.allowedIdentityProviders[$index]" name="identityProvider.id" id="identityProvider.id" value="identityProvider.id" kc-onoffswitch-model />
+                    <input ng-model="oauth.identityProviders[$index].id" name="identityProvider.identityProvider.id" id="identityProvider.identityProvider.id" value="identityProvider.identityProvider.id" onoffswitchmodel />
+                </div>
+                <div data-ng-show="oauth.identityProviders[$index].id">
+                    <label class="col-sm-2 control-label" for="{{identityProvider.identityProvider.id}}retrieveToken">Can Retrieve Token&nbsp;<span tooltip-placement="right" tooltip="If disabled, the application can not retrieve tokens from the identity provider." class="fa fa-info-circle"></span></label>
+                    <div class="col-sm-4">
+                        <input ng-model="oauth.identityProviders[$index].retrieveToken" name="identityProvider.identityProvider.id + 'retrieveToken'" id="identityProvider.identityProvider.id + 'retrieveToken'" value="true" onoffswitchmodel />
+                    </div>
                 </div>
             </div>
             <div class="pull-right form-actions">
-                <button kc-save>Save</button>
+                <button kc-reset data-ng-show="changed">Clear changes</button>
+                <button kc-save data-ng-show="changed">Save</button>
             </div>
         </form>
     </div>
diff --git a/model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java b/model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java
new file mode 100644
index 0000000..e3b8401
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+/**
+ * @author pedroigor
+ */
+public class ClientIdentityProviderMappingModel {
+
+    private String identityProvider;
+    private boolean retrieveToken;
+
+    public String getIdentityProvider() {
+        return this.identityProvider;
+    }
+
+    public void setIdentityProvider(String identityProviderModel) {
+        this.identityProvider = identityProviderModel;
+    }
+
+    public boolean isRetrieveToken() {
+        return this.retrieveToken;
+    }
+
+    public void setRetrieveToken(boolean retrieveToken) {
+        this.retrieveToken = retrieveToken;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 5354d04..3ebdc2a 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -98,11 +98,10 @@ public interface ClientModel {
 
     void setNotBefore(int notBefore);
 
-    void updateAllowedIdentityProviders(List<String> identityProviders);
-
-    List<String> getAllowedIdentityProviders();
-
+    void updateAllowedIdentityProviders(List<ClientIdentityProviderMappingModel> identityProviders);
+    List<ClientIdentityProviderMappingModel> getIdentityProviders();
     boolean hasIdentityProvider(String providerId);
+    boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId);
 
     Set<ProtocolMapperModel> getProtocolMappers();
     void addProtocolMappers(Set<String> mapperIds);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index 3d43fee..134d1bf 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -29,7 +29,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
     private List<String> webOrigins = new ArrayList<String>();
     private List<String> redirectUris = new ArrayList<String>();
     private List<String> scopeIds = new ArrayList<String>();
-    private List<String> allowedIdentityProviders = new ArrayList<String>();
+    private List<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
     private Set<String> protocolMappers = new HashSet<String>();
 
     public String getName() {
@@ -144,12 +144,12 @@ public class ClientEntity extends AbstractIdentifiableEntity {
         this.frontchannelLogout = frontchannelLogout;
     }
 
-    public List<String> getAllowedIdentityProviders() {
-        return this.allowedIdentityProviders;
+    public List<ClientIdentityProviderMappingEntity> getIdentityProviders() {
+        return this.identityProviders;
     }
 
-    public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
-        this.allowedIdentityProviders = allowedIdentityProviders;
+    public void setIdentityProviders(List<ClientIdentityProviderMappingEntity> identityProviders) {
+        this.identityProviders = identityProviders;
     }
 
     public Set<String> getProtocolMappers() {
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java
new file mode 100644
index 0000000..a788aac
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.entities;
+
+/**
+ * @author pedroigor
+ */
+public class ClientIdentityProviderMappingEntity {
+
+    private String id;
+    private Boolean retrieveToken;
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Boolean isRetrieveToken() {
+        return this.retrieveToken;
+    }
+
+    public void setRetrieveToken(Boolean retrieveToken) {
+        this.retrieveToken = retrieveToken;
+    }
+
+}
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 522c965..7bd7cfa 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
@@ -3,6 +3,7 @@ package org.keycloak.models.utils;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClaimTypeModel;
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
@@ -19,6 +20,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.ClaimRepresentation;
 import org.keycloak.representations.idm.ClaimTypeRepresentation;
+import org.keycloak.representations.idm.ClientIdentityProviderMappingRepresentation;
 import org.keycloak.representations.idm.ClientProtocolMappingRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -262,8 +264,8 @@ public class ModelToRepresentation {
             rep.setRegisteredNodes(new HashMap<String, Integer>(applicationModel.getRegisteredNodes()));
         }
 
-        if (!applicationModel.getAllowedIdentityProviders().isEmpty()) {
-            rep.setAllowedIdentityProviders(applicationModel.getAllowedIdentityProviders());
+        if (!applicationModel.getIdentityProviders().isEmpty()) {
+            rep.setIdentityProviders(toRepresentation(applicationModel.getIdentityProviders()));
         }
 
         if (!applicationModel.getProtocolMappers().isEmpty()) {
@@ -279,6 +281,21 @@ public class ModelToRepresentation {
         return rep;
     }
 
+    private static List<ClientIdentityProviderMappingRepresentation> toRepresentation(List<ClientIdentityProviderMappingModel> identityProviders) {
+        ArrayList<ClientIdentityProviderMappingRepresentation> representations = new ArrayList<ClientIdentityProviderMappingRepresentation>();
+
+        for (ClientIdentityProviderMappingModel model : identityProviders) {
+            ClientIdentityProviderMappingRepresentation representation = new ClientIdentityProviderMappingRepresentation();
+
+            representation.setId(model.getIdentityProvider());
+            representation.setRetrieveToken(model.isRetrieveToken());
+
+            representations.add(representation);
+        }
+
+        return representations;
+    }
+
     public static OAuthClientRepresentation toRepresentation(OAuthClientModel model) {
         OAuthClientRepresentation rep = new OAuthClientRepresentation();
         rep.setId(model.getId());
@@ -301,8 +318,8 @@ public class ModelToRepresentation {
         }
         rep.setNotBefore(model.getNotBefore());
 
-        if (!model.getAllowedIdentityProviders().isEmpty()) {
-            rep.setAllowedIdentityProviders(model.getAllowedIdentityProviders());
+        if (!model.getIdentityProviders().isEmpty()) {
+            rep.setIdentityProviders(toRepresentation(model.getIdentityProviders()));
         }
 
         if (!model.getProtocolMappers().isEmpty()) {
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 f296764..2bf61e2 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
@@ -7,6 +7,7 @@ import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClaimTypeModel;
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
@@ -23,6 +24,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.ClaimRepresentation;
 import org.keycloak.representations.idm.ClaimTypeRepresentation;
+import org.keycloak.representations.idm.ClientIdentityProviderMappingRepresentation;
 import org.keycloak.representations.idm.ClientProtocolMappingRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -473,17 +475,7 @@ public class RepresentationToModel {
             applicationModel.setProtocolMappers(ids);
         }
 
-        List<String> allowedIdentityProviders = resourceRep.getAllowedIdentityProviders();
-
-        if (allowedIdentityProviders == null || allowedIdentityProviders.isEmpty()) {
-            allowedIdentityProviders = new ArrayList<String>();
-
-            for (IdentityProviderModel identityProvider : realm.getIdentityProviders()) {
-                allowedIdentityProviders.add(identityProvider.getId());
-            }
-        }
-
-        applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders);
+        applicationModel.updateAllowedIdentityProviders(toModel(resourceRep.getIdentityProviders(), realm));
 
         return applicationModel;
     }
@@ -536,9 +528,7 @@ public class RepresentationToModel {
             setClaims(resource, rep.getClaims());
         }
 
-        if (rep.getAllowedIdentityProviders() != null) {
-            resource.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders());
-        }
+        updateClientIdentityProvides(rep.getIdentityProviders(), resource);
     }
 
     public static void setClaims(ClientModel model, ClaimRepresentation rep) {
@@ -613,17 +603,7 @@ public class RepresentationToModel {
     public static OAuthClientModel createOAuthClient(OAuthClientRepresentation rep, RealmModel realm) {
         OAuthClientModel model = createOAuthClient(rep.getId(), rep.getName(), realm);
 
-        List<String> allowedIdentityProviders = rep.getAllowedIdentityProviders();
-
-        if (allowedIdentityProviders == null || allowedIdentityProviders.isEmpty()) {
-            allowedIdentityProviders = new ArrayList<String>();
-
-            for (IdentityProviderModel identityProvider : realm.getIdentityProviders()) {
-                allowedIdentityProviders.add(identityProvider.getId());
-            }
-        }
-
-        model.updateAllowedIdentityProviders(allowedIdentityProviders);
+        model.updateAllowedIdentityProviders(toModel(rep.getIdentityProviders(), realm));
 
         updateOAuthClient(rep, model);
         return model;
@@ -667,9 +647,7 @@ public class RepresentationToModel {
             }
         }
 
-        if (rep.getAllowedIdentityProviders() != null) {
-            model.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders());
-        }
+        updateClientIdentityProvides(rep.getIdentityProviders(), model);
 
         if (rep.getProtocolMappers() != null) {
             Set<String> ids = new HashSet<String>();
@@ -868,4 +846,48 @@ public class RepresentationToModel {
         model.setConfig(rep.getConfig());
         return model;
     }
+
+    private static List<ClientIdentityProviderMappingModel> toModel(List<ClientIdentityProviderMappingRepresentation> repIdentityProviders, RealmModel realm) {
+        List<ClientIdentityProviderMappingModel> allowedIdentityProviders = new ArrayList<ClientIdentityProviderMappingModel>();
+
+        if (repIdentityProviders == null || repIdentityProviders.isEmpty()) {
+            allowedIdentityProviders = new ArrayList<ClientIdentityProviderMappingModel>();
+
+            for (IdentityProviderModel identityProvider : realm.getIdentityProviders()) {
+                ClientIdentityProviderMappingModel identityProviderMapping = new ClientIdentityProviderMappingModel();
+
+                identityProviderMapping.setIdentityProvider(identityProvider.getId());
+
+                allowedIdentityProviders.add(identityProviderMapping);
+            }
+        } else {
+            for (ClientIdentityProviderMappingRepresentation rep : repIdentityProviders) {
+                ClientIdentityProviderMappingModel identityProviderMapping = new ClientIdentityProviderMappingModel();
+
+                identityProviderMapping.setIdentityProvider(rep.getId());
+                identityProviderMapping.setRetrieveToken(rep.isRetrieveToken());
+
+                allowedIdentityProviders.add(identityProviderMapping);
+            }
+        }
+
+        return allowedIdentityProviders;
+    }
+
+    private static void updateClientIdentityProvides(List<ClientIdentityProviderMappingRepresentation> identityProviders, ClientModel resource) {
+        if (identityProviders != null) {
+            List<ClientIdentityProviderMappingModel> allowedIdentityProviders = new ArrayList<ClientIdentityProviderMappingModel>();
+
+            for (ClientIdentityProviderMappingRepresentation mappingRepresentation : identityProviders) {
+                ClientIdentityProviderMappingModel identityProviderMapping = new ClientIdentityProviderMappingModel();
+
+                identityProviderMapping.setIdentityProvider(mappingRepresentation.getId());
+                identityProviderMapping.setRetrieveToken(mappingRepresentation.isRetrieveToken());
+
+                allowedIdentityProviders.add(identityProviderMapping);
+            }
+
+            resource.updateAllowedIdentityProviders(allowedIdentityProviders);
+        }
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java
index 0fc38bc..a17cc7c 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java
@@ -1,5 +1,6 @@
 package org.keycloak.models.cache;
 
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -263,15 +264,15 @@ public abstract class ClientAdapter implements ClientModel {
     }
 
     @Override
-    public void updateAllowedIdentityProviders(List<String> identityProviders) {
+    public void updateAllowedIdentityProviders(List<ClientIdentityProviderMappingModel> identityProviders) {
         getDelegateForUpdate();
         updatedClient.updateAllowedIdentityProviders(identityProviders);
     }
 
     @Override
-    public List<String> getAllowedIdentityProviders() {
-        if (updatedClient != null) return updatedClient.getAllowedIdentityProviders();
-        return cachedClient.getAllowedIdentityProviders();
+    public List<ClientIdentityProviderMappingModel> getIdentityProviders() {
+        if (updatedClient != null) return updatedClient.getIdentityProviders();
+        return cachedClient.getIdentityProviders();
     }
 
     @Override
@@ -281,6 +282,12 @@ public abstract class ClientAdapter implements ClientModel {
     }
 
     @Override
+    public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) {
+        if (updatedClient != null) return updatedClient.isAllowedRetrieveTokenFromIdentityProvider(providerId);
+        return cachedClient.isAllowedRetrieveTokenFromIdentityProvider(providerId);
+    }
+
+    @Override
     public Set<ProtocolMapperModel> getProtocolMappers() {
         if (updatedClient != null) return updatedClient.getProtocolMappers();
         return cachedClient.getProtocolClaimMappings();    }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 1029713..e9a8a89 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -1,5 +1,6 @@
 package org.keycloak.models.cache.entities;
 
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -35,7 +36,7 @@ public class CachedClient {
     protected int notBefore;
     protected Set<String> scope = new HashSet<String>();
     protected Set<String> webOrigins = new HashSet<String>();
-    private List<String> allowedIdentityProviders = new ArrayList<String>();
+    private List<ClientIdentityProviderMappingModel> identityProviders = new ArrayList<ClientIdentityProviderMappingModel>();
     private Set<ProtocolMapperModel> protocolClaimMappings = new HashSet<ProtocolMapperModel>();
 
     public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
@@ -57,7 +58,7 @@ public class CachedClient {
         for (RoleModel role : model.getScopeMappings())  {
             scope.add(role.getId());
         }
-        this.allowedIdentityProviders = model.getAllowedIdentityProviders();
+        this.identityProviders = model.getIdentityProviders();
         protocolClaimMappings.addAll(model.getProtocolMappers());
     }
 
@@ -125,15 +126,31 @@ public class CachedClient {
         return frontchannelLogout;
     }
 
-    public List<String> getAllowedIdentityProviders() {
-        return this.allowedIdentityProviders;
+    public List<ClientIdentityProviderMappingModel> getIdentityProviders() {
+        return this.identityProviders;
     }
 
     public boolean hasIdentityProvider(String providerId) {
-        return this.allowedIdentityProviders.contains(providerId);
+        for (ClientIdentityProviderMappingModel model : getIdentityProviders()) {
+            if (model.getIdentityProvider().equals(providerId)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     public Set<ProtocolMapperModel> getProtocolClaimMappings() {
         return protocolClaimMappings;
     }
+
+    public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) {
+        for (ClientIdentityProviderMappingModel model : getIdentityProviders()) {
+            if (model.getIdentityProvider().equals(providerId)) {
+                return model.isRetrieveToken();
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index c437014..04af5ee 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -1,14 +1,15 @@
 package org.keycloak.models.jpa;
 
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.jpa.entities.ClientEntity;
+import org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity;
 import org.keycloak.models.jpa.entities.IdentityProviderEntity;
 import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
-import org.keycloak.models.jpa.entities.RealmEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.jpa.entities.ScopeMappingEntity;
 
@@ -303,47 +304,96 @@ public abstract class ClientAdapter implements ClientModel {
     }
 
     @Override
-    public void updateAllowedIdentityProviders(List<String> identityProviders) {
-        Collection<IdentityProviderEntity> entities = entity.getAllowedIdentityProviders();
+    public void updateAllowedIdentityProviders(List<ClientIdentityProviderMappingModel> identityProviders) {
+        Collection<ClientIdentityProviderMappingEntity> entities = entity.getIdentityProviders();
         Set<String> already = new HashSet<String>();
-        List<IdentityProviderEntity> remove = new ArrayList<IdentityProviderEntity>();
-        for (IdentityProviderEntity rel : entities) {
-            if (!contains(rel.getId(), identityProviders.toArray(new String[identityProviders.size()]))) {
-                remove.add(rel);
+        List<ClientIdentityProviderMappingEntity> remove = new ArrayList<ClientIdentityProviderMappingEntity>();
+
+        for (ClientIdentityProviderMappingEntity entity : entities) {
+            IdentityProviderEntity identityProvider = entity.getIdentityProvider();
+            boolean toRemove = true;
+
+            for (ClientIdentityProviderMappingModel model : identityProviders) {
+                if (model.getIdentityProvider().equals(identityProvider.getId())) {
+                    toRemove = false;
+                    break;
+                }
+            }
+
+            if (toRemove) {
+                remove.add(entity);
             } else {
-                already.add(rel.getId());
+                already.add(entity.getIdentityProvider().getId());
             }
         }
-        for (IdentityProviderEntity entity : remove) {
+        for (ClientIdentityProviderMappingEntity entity : remove) {
             entities.remove(entity);
+            em.remove(entity);
         }
         em.flush();
-        for (String providerId : identityProviders) {
-            if (!already.contains(providerId)) {
-                TypedQuery<IdentityProviderEntity> query = em.createNamedQuery("findIdentityProviderById", IdentityProviderEntity.class).setParameter("id", providerId);
-                IdentityProviderEntity providerEntity = query.getSingleResult();
-                entities.add(providerEntity);
+        for (ClientIdentityProviderMappingModel model : identityProviders) {
+            ClientIdentityProviderMappingEntity mappingEntity = null;
+
+            if (!already.contains(model.getIdentityProvider())) {
+                mappingEntity = new ClientIdentityProviderMappingEntity();
+                entities.add(mappingEntity);
+            } else {
+                for (ClientIdentityProviderMappingEntity entity : entities) {
+                    if (entity.getIdentityProvider().getId().equals(model.getIdentityProvider())) {
+                        mappingEntity = entity;
+                        break;
+                    }
+                }
             }
+
+            TypedQuery<IdentityProviderEntity> query = em.createNamedQuery("findIdentityProviderById", IdentityProviderEntity.class).setParameter("id", model.getIdentityProvider());
+            IdentityProviderEntity identityProviderEntity = query.getSingleResult();
+
+            mappingEntity.setIdentityProvider(identityProviderEntity);
+            mappingEntity.setClient(this.entity);
+            mappingEntity.setRetrieveToken(model.isRetrieveToken());
+
+            em.persist(mappingEntity);
         }
         em.flush();
     }
 
     @Override
-    public List<String> getAllowedIdentityProviders() {
-        Collection<IdentityProviderEntity> entities = entity.getAllowedIdentityProviders();
-        List<String> providers = new ArrayList<String>();
+    public List<ClientIdentityProviderMappingModel> getIdentityProviders() {
+        List<ClientIdentityProviderMappingModel> models = new ArrayList<ClientIdentityProviderMappingModel>();
+
+        for (ClientIdentityProviderMappingEntity entity : this.entity.getIdentityProviders()) {
+            ClientIdentityProviderMappingModel model = new ClientIdentityProviderMappingModel();
 
-        for (IdentityProviderEntity entity : entities) {
-            providers.add(entity.getId());
+            model.setIdentityProvider(entity.getIdentityProvider().getId());
+            model.setRetrieveToken(entity.isRetrieveToken());
+
+            models.add(model);
         }
 
-        return providers;
+        return models;
     }
 
     @Override
     public boolean hasIdentityProvider(String providerId) {
-        List<String> allowedIdentityProviders = getAllowedIdentityProviders();
-        return allowedIdentityProviders.contains(providerId);
+        for (ClientIdentityProviderMappingModel model : getIdentityProviders()) {
+            if (model.getIdentityProvider().equals(providerId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) {
+        for (ClientIdentityProviderMappingModel model : getIdentityProviders()) {
+            if (model.getIdentityProvider().equals(providerId)) {
+                return model.isRetrieveToken();
+            }
+        }
+
+        return false;
     }
 
     public static boolean contains(String str, String[] array) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 817fd80..dcf33cb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -73,9 +73,8 @@ public abstract class ClientEntity {
     @CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") })
     protected Map<String, String> attributes = new HashMap<String, String>();
 
-    @OneToMany(fetch = FetchType.LAZY)
-    @JoinTable(name="CLIENT_ALLOWED_IDENTITY_PROVIDER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="INTERNAL_ID")})
-    Collection<IdentityProviderEntity> allowedIdentityProviders = new ArrayList<IdentityProviderEntity>();
+    @OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.REMOVE)
+    Collection<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
 
     @OneToMany(fetch = FetchType.LAZY)
     @JoinTable(name="CLIENT_PROTOCOL_MAPPER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="MAPPING_ID")})
@@ -193,12 +192,12 @@ public abstract class ClientEntity {
         this.frontchannelLogout = frontchannelLogout;
     }
 
-    public Collection<IdentityProviderEntity> getAllowedIdentityProviders() {
-        return this.allowedIdentityProviders;
+    public Collection<ClientIdentityProviderMappingEntity> getIdentityProviders() {
+        return this.identityProviders;
     }
 
-    public void setAllowedIdentityProviders(Collection<IdentityProviderEntity> allowedIdentityProviders) {
-        this.allowedIdentityProviders = allowedIdentityProviders;
+    public void setIdentityProviders(Collection<ClientIdentityProviderMappingEntity> identityProviders) {
+        this.identityProviders = identityProviders;
     }
 
     public Collection<ProtocolMapperEntity> getProtocolMappers() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java
new file mode 100755
index 0000000..fe8485d
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java
@@ -0,0 +1,121 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author pedroigor
+ */
+@Table(name="CLIENT_IDENTITY_PROVIDER_MAPPING")
+@Entity
+@IdClass(ClientIdentityProviderMappingEntity.Key.class)
+public class ClientIdentityProviderMappingEntity {
+
+    @Id
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "CLIENT_ID")
+    private ClientEntity client;
+
+    @Id
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "IDENTITY_PROVIDER_ID")
+    private IdentityProviderEntity identityProvider;
+
+    @Column(name = "RETRIEVE_TOKEN")
+    private boolean retrieveToken;
+
+    public ClientEntity getClient() {
+        return this.client;
+    }
+
+    public void setClient(ClientEntity client) {
+        this.client = client;
+    }
+
+    public IdentityProviderEntity getIdentityProvider() {
+        return this.identityProvider;
+    }
+
+    public void setIdentityProvider(IdentityProviderEntity identityProvider) {
+        this.identityProvider = identityProvider;
+    }
+
+    public void setRetrieveToken(boolean retrieveToken) {
+        this.retrieveToken = retrieveToken;
+    }
+
+    public boolean isRetrieveToken() {
+        return retrieveToken;
+    }
+
+    public static class Key implements Serializable {
+
+        private ClientEntity client;
+        private IdentityProviderEntity identityProvider;
+
+        public Key() {
+        }
+
+        public Key(ClientEntity client, IdentityProviderEntity identityProvider) {
+            this.client = client;
+            this.identityProvider = identityProvider;
+        }
+
+        public ClientEntity getUser() {
+            return client;
+        }
+
+        public IdentityProviderEntity getIdentityProvider() {
+            return identityProvider;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (identityProvider != null ? !identityProvider.getId().equals(key.identityProvider.getId()) : key.identityProvider != null)
+                return false;
+            if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = client != null ? client.getId().hashCode() : 0;
+            result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ClientIdentityProviderMappingEntity key = (ClientIdentityProviderMappingEntity) o;
+
+        if (identityProvider != null ? !identityProvider.getId().equals(key.identityProvider.getId()) : key.identityProvider != null)
+            return false;
+        if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = client != null ? client.getId().hashCode() : 0;
+        result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 3c47282..c0202c1 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.models.mongo.keycloak.adapters;
 
 import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
@@ -9,6 +10,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.entities.ClientEntity;
+import org.keycloak.models.entities.ClientIdentityProviderMappingEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
 
@@ -323,24 +325,58 @@ public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends A
     }
 
     @Override
-    public void updateAllowedIdentityProviders(List<String> identityProviders) {
-        List<String> providerIds = new ArrayList<String>();
-        for (String providerId : identityProviders) {
-            providerIds.add(providerId);
+    public void updateAllowedIdentityProviders(List<ClientIdentityProviderMappingModel> identityProviders) {
+        List<ClientIdentityProviderMappingEntity> stored = getMongoEntityAsClient().getIdentityProviders();
+
+        for (ClientIdentityProviderMappingModel model : identityProviders) {
+            ClientIdentityProviderMappingEntity entity = new ClientIdentityProviderMappingEntity();
+
+            entity.setId(model.getIdentityProvider());
+            entity.setRetrieveToken(model.isRetrieveToken());
         }
 
-        getMongoEntityAsClient().setAllowedIdentityProviders(identityProviders);
+        getMongoEntityAsClient().setIdentityProviders(stored);
         updateMongoEntity();
     }
 
     @Override
-    public List<String> getAllowedIdentityProviders() {
-        return getMongoEntityAsClient().getAllowedIdentityProviders();
+    public List<ClientIdentityProviderMappingModel> getIdentityProviders() {
+        List<ClientIdentityProviderMappingModel> models = new ArrayList<ClientIdentityProviderMappingModel>();
+
+        for (ClientIdentityProviderMappingEntity entity : getMongoEntityAsClient().getIdentityProviders()) {
+            ClientIdentityProviderMappingModel model = new ClientIdentityProviderMappingModel();
+
+            model.setIdentityProvider(entity.getId());
+            model.setRetrieveToken(entity.isRetrieveToken());
+
+            models.add(model);
+        }
+
+        return models;
     }
 
     @Override
     public boolean hasIdentityProvider(String providerId) {
-        List<String> allowedIdentityProviders = getMongoEntityAsClient().getAllowedIdentityProviders();
-        return allowedIdentityProviders.contains(providerId);
+        for (ClientIdentityProviderMappingEntity identityProviderMappingModel : getMongoEntityAsClient().getIdentityProviders()) {
+            String identityProvider = identityProviderMappingModel.getId();
+
+            if (identityProvider.equals(providerId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) {
+        for (ClientIdentityProviderMappingEntity identityProviderMappingModel : getMongoEntityAsClient().getIdentityProviders()) {
+            if (identityProviderMappingModel.getId().equals(providerId)) {
+                return identityProviderMappingModel.isRetrieveToken();
+            }
+        }
+
+        return false;
     }
+
 }
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 e8b184e..bac9b03 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
@@ -1,6 +1,7 @@
 package org.keycloak.services.resources.admin;
 
 import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -17,6 +18,7 @@ import javax.ws.rs.GET;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Response;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -63,11 +65,15 @@ public class IdentityProviderResource {
 
     private void removeClientIdentityProviders(List<? extends ClientModel> clients, IdentityProviderModel identityProvider) {
         for (ClientModel clientModel : clients) {
-            List<String> allowedIdentityProviders = clientModel.getAllowedIdentityProviders();
+            List<ClientIdentityProviderMappingModel> identityProviders = clientModel.getIdentityProviders();
 
-            allowedIdentityProviders.remove(identityProvider.getId());
+            for (ClientIdentityProviderMappingModel providerMappingModel : new ArrayList<ClientIdentityProviderMappingModel>(identityProviders)) {
+                if (providerMappingModel.getIdentityProvider().equals(identityProvider.getId())) {
+                    identityProviders.remove(providerMappingModel);
+                }
+            }
 
-            clientModel.updateAllowedIdentityProviders(allowedIdentityProviders);
+            clientModel.updateAllowedIdentityProviders(identityProviders);
         }
     }
 
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 d44e464..d7d1a67 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
@@ -7,6 +7,7 @@ import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.models.ClientIdentityProviderMappingModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -179,9 +180,12 @@ public class IdentityProvidersResource {
 
     private void updateClientIdentityProviders(List<? extends ClientModel> clients, IdentityProviderRepresentation identityProvider) {
         for (ClientModel clientModel : clients) {
-            List<String> allowedIdentityProviders = clientModel.getAllowedIdentityProviders();
+            List<ClientIdentityProviderMappingModel> allowedIdentityProviders = clientModel.getIdentityProviders();
+            ClientIdentityProviderMappingModel providerMappingModel = new ClientIdentityProviderMappingModel();
 
-            allowedIdentityProviders.add(identityProvider.getId());
+            providerMappingModel.setIdentityProvider(identityProvider.getId());
+
+            allowedIdentityProviders.add(providerMappingModel);
 
             clientModel.updateAllowedIdentityProviders(allowedIdentityProviders);
         }
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 5de4015..7fb4209 100644
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -186,6 +186,10 @@ public class IdentityBrokerService {
                     return corsResponse(badRequest("Client [" + audience + "] not authorized."), clientModel);
                 }
 
+                if (!clientModel.isAllowedRetrieveTokenFromIdentityProvider(providerId)) {
+                    return corsResponse(badRequest("Client [" + audience + "] not authorized to retrieve tokens from identity provider [" + providerId + "]."), clientModel);
+                }
+
                 if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) {
                     return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo)
                             .setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders()))
@@ -323,7 +327,7 @@ public class IdentityBrokerService {
                     federatedUser.addRequiredAction(UPDATE_PROFILE);
                 }
             } catch (Exception e) {
-                return redirectToLoginPage(e.getMessage(), clientCode);
+                return redirectToLoginPage(e, clientCode);
             }
         }
 
@@ -439,16 +443,21 @@ public class IdentityBrokerService {
     }
 
     private Response redirectToErrorPage(String message, Throwable throwable) {
+        if (message == null) {
+            message = "Unexpected error when authenticating with identity provider";
+        }
+
         fireErrorEvent(message, throwable);
         return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message);
     }
 
-    private Response badRequest(String message) {
-        fireErrorEvent(message);
-        return Flows.errors().error(message, Status.BAD_REQUEST);
-    }
+    private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) {
+        String message = t.getMessage();
+
+        if (message == null) {
+            message = "Unexpected error when authenticating with identity provider";
+        }
 
-    private Response redirectToLoginPage(String message, ClientSessionCode clientCode) {
         fireErrorEvent(message);
         return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo)
                 .setClientSessionCode(clientCode.getCode())
@@ -456,6 +465,11 @@ public class IdentityBrokerService {
                 .createLogin();
     }
 
+    private Response badRequest(String message) {
+        fireErrorEvent(message);
+        return Flows.errors().error(message, Status.BAD_REQUEST);
+    }
+
     private IdentityProvider getIdentityProvider(String providerId) {
         IdentityProviderModel identityProviderModel = this.realmModel.getIdentityProviderById(providerId);
 
@@ -513,7 +527,11 @@ public class IdentityBrokerService {
         FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(updatedIdentity.getIdentityProviderId(), updatedIdentity.getId(),
                 updatedIdentity.getUsername(), updatedIdentity.getToken());
         // Check if no user already exists with this username or email
-        UserModel existingUser = this.session.users().getUserByEmail(updatedIdentity.getEmail(), this.realmModel);
+        UserModel existingUser = null;
+
+        if (updatedIdentity.getEmail() != null) {
+            existingUser = this.session.users().getUserByEmail(updatedIdentity.getEmail(), this.realmModel);
+        }
 
         if (existingUser != null) {
             fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS);
diff --git a/testsuite/integration/README.md b/testsuite/integration/README.md
index a8d0671..f76392e 100644
--- a/testsuite/integration/README.md
+++ b/testsuite/integration/README.md
@@ -100,12 +100,12 @@ To start a ApacheDS based LDAP server for testing LDAP sending run:
     
 There are additional system properties you can use to configure (See EmbeddedServersFactory class for details). Once done, you can create LDAP Federation provider
 in Keycloak admin console with the settings like:
-Vendor: Other
-Connection URL: ldap://localhost:10389
-Base DN: dc=keycloak,dc=org
-User DN Suffix: ou=People,dc=keycloak,dc=org
-Bind DN: uid=admin,ou=system
-Bind credential: secret
+* Vendor: Other
+* Connection URL: ldap://localhost:10389
+* Base DN: dc=keycloak,dc=org
+* User DN Suffix: ou=People,dc=keycloak,dc=org
+* Bind DN: uid=admin,ou=system
+* Bind credential: secret
 
 Kerberos server
 ---------------
@@ -114,10 +114,40 @@ To start a ApacheDS based Kerberos server for testing Kerberos + LDAP sending ru
     
     mvn exec:java -Pkerberos
     
-There are additional system properties you can use to configure (See EmbeddedServersFactory class for details). Once done, you can create LDAP Federation provider
-in Keycloak admin console with same settings like mentioned in previous LDAP section. And you can enable Kerberos with the settings like:
+There are additional system properties you can use to configure (See EmbeddedServersFactory class for details) but for testing purposes default values should be good.
+By default ApacheDS LDAP server will be running on localhost:10389 and Kerberos KDC on localhost:6088 . LDAP will import initial data from [src/main/resources/kerberos/users-kerberos.ldif](src/main/resources/kerberos/users-kerberos.ldif) . 
+
+Once kerberos is running, you can create LDAP Federation provider in Keycloak admin console with same settings like mentioned in previous LDAP section. 
+But additionally you can enable Kerberos authentication in LDAP provider with the settings like:
+
+* Kerberos realm: KEYCLOAK.ORG
+* Server Principal: HTTP/localhost@KEYCLOAK.ORG
+* KeyTab: $KEYCLOAK_SOURCES/testsuite/integration/src/main/resources/kerberos/http.keytab (Replace $KEYCLOAK_SOURCES with correct absolute path of your sources)
+
+Once you do this, you should also ensure that your Kerberos client configuration file is properly configured with KEYCLOAK.ORG domain. 
+See [src/main/resources/kerberos/test-krb5.conf](src/main/resources/kerberos/test-krb5.conf) for inspiration. The location of Kerberos configuration file 
+is platform dependent (In linux it's file `/etc/krb5.conf` )
+
+Then you need to configure your browser to allow SPNEGO/Kerberos login from `localhost` .
+
+Exact steps are again browser dependent. For Firefox see for example [http://www.microhowto.info/howto/configure_firefox_to_authenticate_using_spnego_and_kerberos.html](http://www.microhowto.info/howto/configure_firefox_to_authenticate_using_spnego_and_kerberos.html) . 
+URI `localhost` must be allowed in `network.negotiate-auth.trusted-uris` config option. 
+
+For Chrome, you just need to run the browser with command similar to this (more details in Chrome documentation):
+
+```
+/usr/bin/google-chrome-stable --auth-server-whitelist="localhost"
+```
+
+
+Finally test the integration by retrieve kerberos ticket. In many OS you can achieve this by running command from CMD like:
+                                          
+```
+kinit hnelson@KEYCLOAK.ORG
+```
+                        
+and provide password `secret`
 
-Server Principal: HTTP/localhost@KEYCLOAK.ORG
-KeyTab: $KEYCLOAK_SOURCES/testsuite/integration/src/main/resources/kerberos/http.keytab
+Now when you access `http://localhost:8081/auth/realms/master/account` you should be logged in automatically as user `hnelson` . 
 
 
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
index ff5b779..527c9b3 100644
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
@@ -54,7 +54,7 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
     public void init() throws Exception {
         super.init();
 
-        log.info("Creating KDC server");
+        log.info("Creating KDC server. kerberosRealm: " + kerberosRealm + ", kdcPort: " + kdcPort);
         createAndStartKdcServer();
     }
 
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 6921ec3..dd034c1 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -25,6 +25,8 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientIdentityProviderMappingModel;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -156,11 +158,18 @@ public abstract class AbstractIdentityProviderTest {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
         RealmModel realm = getRealm();
         ApplicationModel applicationModel = realm.getApplicationByName("test-app");
-        List<String> allowedIdentityProviders = applicationModel.getAllowedIdentityProviders();
+        List<ClientIdentityProviderMappingModel> allowedIdentityProviders = applicationModel.getIdentityProviders();
+        ClientIdentityProviderMappingModel mapping = null;
 
-        assertTrue(allowedIdentityProviders.contains(identityProviderModel.getId()));
+        for (ClientIdentityProviderMappingModel model : allowedIdentityProviders) {
+            if (model.getIdentityProvider().equals(identityProviderModel.getId())) {
+                mapping = model;
+            }
+        }
 
-        allowedIdentityProviders.remove(identityProviderModel.getId());
+        assertNotNull(mapping);
+
+        allowedIdentityProviders.remove(mapping);
 
         this.driver.navigate().to("http://localhost:8081/test-app/");
 
@@ -173,7 +182,7 @@ public abstract class AbstractIdentityProviderTest {
 
         }
 
-        allowedIdentityProviders.add(identityProviderModel.getId());
+        allowedIdentityProviders.add(mapping);
 
         applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders);
 
@@ -317,6 +326,18 @@ public abstract class AbstractIdentityProviderTest {
 
         assertNotNull(identityModel.getToken());
 
+        ClientModel clientModel = realm.findClient("test-app");
+        ClientIdentityProviderMappingModel providerMappingModel = null;
+
+        for (ClientIdentityProviderMappingModel identityProviderMappingModel : clientModel.getIdentityProviders()) {
+            if (identityProviderMappingModel.getIdentityProvider().equals(getProviderId())) {
+                providerMappingModel = identityProviderMappingModel;
+                break;
+            }
+        }
+
+        providerMappingModel.setRetrieveToken(false);
+
         UserSessionStatus userSessionStatus = retrieveSessionStatus();
         String accessToken = userSessionStatus.getAccessTokenString();
         URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
@@ -331,6 +352,14 @@ public abstract class AbstractIdentityProviderTest {
         WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
         Response response = tokenEndpoint.request().get();
 
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        providerMappingModel.setRetrieveToken(true);
+
+        client = ClientBuilder.newBuilder().register(authFilter).build();
+        tokenEndpoint = client.target(tokenEndpointUrl);
+        response = tokenEndpoint.request().get();
+
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         assertNotNull(response.readEntity(String.class));
 
@@ -375,6 +404,18 @@ public abstract class AbstractIdentityProviderTest {
 
         assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
 
+        ClientModel clientModel = getRealm().findClient("third-party");
+        ClientIdentityProviderMappingModel providerMappingModel = null;
+
+        for (ClientIdentityProviderMappingModel identityProviderMappingModel : clientModel.getIdentityProviders()) {
+            if (identityProviderMappingModel.getIdentityProvider().equals(getProviderId())) {
+                providerMappingModel = identityProviderMappingModel;
+                break;
+            }
+        }
+
+        providerMappingModel.setRetrieveToken(true);
+
         AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
         URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), getRealm().getName());
         String authHeader = "Bearer " + accessToken.getAccessToken();
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 d654d1e..8f43860 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -25,6 +25,8 @@ 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.ClientIdentityProviderMappingModel;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -113,6 +115,31 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertFalse(identityProviderModel.isAuthenticateByDefault());
     }
 
+    @Test
+    public void testApplicationIdentityProviders() throws Exception {
+        RealmModel realm = installTestRealm();
+
+        ClientModel client = realm.findClient("test-app-with-allowed-providers");
+        List<ClientIdentityProviderMappingModel> identityProviders = client.getIdentityProviders();
+
+        assertEquals(1, identityProviders.size());
+
+        ClientIdentityProviderMappingModel identityProviderMappingModel = identityProviders.get(0);
+
+        assertEquals("kc-oidc-idp", identityProviderMappingModel.getIdentityProvider());
+        assertEquals(false, identityProviderMappingModel.isRetrieveToken());
+
+        identityProviders.remove(identityProviderMappingModel);
+
+        client.updateAllowedIdentityProviders(identityProviders);
+
+        client = realm.findClientById(client.getId());
+        identityProviders = client.getIdentityProviders();
+
+        assertEquals(0, identityProviders.size());
+    }
+
+
     private void assertIdentityProviderConfig(List<IdentityProviderModel> identityProviders) {
         assertFalse(identityProviders.isEmpty());
 
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 322cebe..c567c17 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
@@ -197,8 +197,11 @@
             "/test-app/*"
           ],
           "webOrigins": [],
-          "allowedIdentityProviders": [
-            "kc-oidc-idp"
+          "identityProviders": [
+            {
+              "id": "kc-oidc-idp",
+              "retrieveToken": false
+            }
           ]
         }
     ],