keycloak-aplcache

Changes

Details

diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java b/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
index ac853f7..8f81a6f 100644
--- a/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
@@ -30,6 +30,7 @@ public class FederatedIdentity {
     private String firstName;
     private String lastName;
     private String email;
+    private String token;
 
     public FederatedIdentity(String id) {
         if (id == null) {
@@ -84,4 +85,11 @@ public class FederatedIdentity {
     }
 
 
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getToken() {
+        return this.token;
+    }
 }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index cfe6f41..f84765a 100644
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -17,9 +17,12 @@
  */
 package org.keycloak.broker.provider;
 
+import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.provider.Provider;
 
+import javax.ws.rs.core.Response;
+
 /**
  * @author Pedro Igor
  */
@@ -64,4 +67,6 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
      * @return
      */
     AuthenticationResponse handleResponse(AuthenticationRequest request);
+
+    Response retrieveToken(FederatedIdentityModel identity);
 }
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 3118cfe..14bc767 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -25,7 +25,9 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
 import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.models.FederatedIdentityModel;
 
+import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
@@ -100,7 +102,13 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
                         .param(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri())
                         .param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE).asString();
 
-                return doHandleResponse(response);
+                FederatedIdentity federatedIdentity = getFederatedIdentity(response);
+
+                if (getConfig().isStoreToken()) {
+                    federatedIdentity.setToken(response);
+                }
+
+                return AuthenticationResponse.end(federatedIdentity);
             }
 
             throw new RuntimeException("No authorization code from identity provider.");
@@ -110,23 +118,22 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
     }
 
     @Override
-    public C getConfig() {
-        return super.getConfig();
+    public Response retrieveToken(FederatedIdentityModel identity) {
+        return Response.ok(identity.getToken()).build();
     }
 
-    protected AuthenticationResponse doHandleResponse(String response) throws IOException {
-        String token = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
-
-        if (token == null) {
-            throw new RuntimeException("No access token from server.");
-        }
-
-        return AuthenticationResponse.end(getFederatedIdentity(token));
+    @Override
+    public C getConfig() {
+        return super.getConfig();
     }
 
-    protected String extractTokenFromResponse(String response, String tokenName) throws IOException {
+    protected String extractTokenFromResponse(String response, String tokenName) {
         if (response.startsWith("{")) {
-            return mapper.readTree(response).get(tokenName).getTextValue();
+            try {
+                return mapper.readTree(response).get(tokenName).getTextValue();
+            } catch (IOException e) {
+                throw new RuntimeException("Could not extract token [" + tokenName + "] from response [" + response + "].", e);
+            }
         } else {
             Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
 
@@ -138,9 +145,21 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
         return null;
     }
 
-    protected FederatedIdentity getFederatedIdentity(String accessToken) {
-        throw new RuntimeException("Not implemented.");
-    };
+    protected FederatedIdentity getFederatedIdentity(String response) {
+        String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
+
+        if (accessToken == null) {
+            throw new RuntimeException("No access token from server.");
+        }
+
+        return doGetFederatedIdentity(accessToken);
+    }
+
+    protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
+        return null;
+    }
+
+    ;
 
     protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
         return UriBuilder.fromPath(getConfig().getAuthorizationUrl())
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index c886b06..50a5f03 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -20,7 +20,6 @@ package org.keycloak.broker.oidc;
 import org.codehaus.jackson.JsonNode;
 import org.keycloak.broker.oidc.util.SimpleHttp;
 import org.keycloak.broker.provider.AuthenticationRequest;
-import org.keycloak.broker.provider.AuthenticationResponse;
 import org.keycloak.broker.provider.FederatedIdentity;
 import org.keycloak.jose.jws.JWSInput;
 
@@ -59,7 +58,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
     }
 
     @Override
-    protected AuthenticationResponse doHandleResponse(String response) throws IOException {
+    protected FederatedIdentity getFederatedIdentity(String response) {
         String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
 
         if (accessToken == null) {
@@ -96,7 +95,11 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
 
             identity.setUsername(preferredUsername);
 
-            return AuthenticationResponse.end(identity);
+            if (getConfig().isStoreToken()) {
+                identity.setToken(response);
+            }
+
+            return identity;
         } catch (Exception e) {
             throw new RuntimeException("Could not fetch attributes from userinfo endpoint.", e);
         }
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 8dc36a1..8e458bc 100644
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
 import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
 import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
 import org.picketlink.common.constants.JBossSAMLConstants;
@@ -54,6 +55,7 @@ import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.xml.namespace.QName;
@@ -140,8 +142,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
 
     @Override
     public AuthenticationResponse handleResponse(AuthenticationRequest request) {
+        String samlResponse = getRequestParameter(request, SAML_RESPONSE_PARAMETER);
+
+        if (samlResponse == null) {
+            throw new RuntimeException("No response from SAML identity provider.");
+        }
+
         try {
-            AssertionType assertion = getAssertion(request);
+            AssertionType assertion = getAssertion(samlResponse, request);
             SubjectType subject = assertion.getSubject();
             STSubType subType = subject.getSubType();
             NameIDType subjectNameID = (NameIDType) subType.getBaseID();
@@ -153,19 +161,22 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                 identity.setEmail(subjectNameID.getValue());
             }
 
+            if (getConfig().isStoreToken()) {
+                identity.setToken(samlResponse);
+            }
+
             return AuthenticationResponse.end(identity);
         } catch (Exception e) {
             throw new RuntimeException("Could not process response from SAML identity provider.", e);
         }
     }
 
-    private AssertionType getAssertion(AuthenticationRequest request) throws Exception {
-        String samlResponse = getRequestParameter(request, SAML_RESPONSE_PARAMETER);
-
-        if (samlResponse == null) {
-            throw new RuntimeException("No response from SAML identity provider.");
-        }
+    @Override
+    public Response retrieveToken(FederatedIdentityModel identity) {
+        return Response.ok(identity.getToken()).build();
+    }
 
+    private AssertionType getAssertion(String samlResponse, AuthenticationRequest request) throws Exception {
         SAML2Request saml2Request = new SAML2Request();
         ResponseType responseType;
 
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 c203b4e..69a7678 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
@@ -8,6 +8,7 @@
             <column name="REALM_ID" type="VARCHAR(255)"/>
             <column name="FEDERATED_USER_ID" type="VARCHAR(255)"/>
             <column name="FEDERATED_USERNAME" type="VARCHAR(255)"/>
+            <column name="TOKEN" type="TEXT"/>
             <column name="USER_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
@@ -21,6 +22,7 @@
             <column name="PROVIDER_NAME" type="VARCHAR(255)"/>
             <column name="PROVIDER_ID" type="VARCHAR(255)"/>
             <column name="UPDATE_PROFILE_FIRST_LOGIN" type="BOOLEAN(1)"/>
+            <column name="STORE_TOKEN" type="BOOLEAN(1)"/>
             <column name="REALM_ID" type="VARCHAR(36)"/>
         </createTable>
         <createTable tableName="IDENTITY_PROVIDER_CONFIG">
@@ -32,6 +34,14 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
+        <createTable tableName="CLIENT_ALLOWED_IDENTITY_PROVIDER">
+            <column name="CLIENT_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="INTERNAL_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
         <addColumn tableName="CLIENT">
             <column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false"/>
         </addColumn>
@@ -41,5 +51,8 @@
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
         <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="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"/>
+        <addUniqueConstraint columnNames="PROVIDER_NONIMAL_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
     </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 82c2ebc..3369893 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -28,6 +28,7 @@ public class ApplicationRepresentation {
     protected Boolean fullScopeAllowed;
     protected Integer nodeReRegistrationTimeout;
     protected Map<String, Integer> registeredNodes;
+    protected List<String> allowedIdentityProviders;
 
     public String getId() {
         return id;
@@ -188,4 +189,12 @@ public class ApplicationRepresentation {
     public void setFrontchannelLogout(Boolean frontchannelLogout) {
         this.frontchannelLogout = frontchannelLogout;
     }
+
+    public List<String> getAllowedIdentityProviders() {
+        return this.allowedIdentityProviders;
+    }
+
+    public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
+        this.allowedIdentityProviders = allowedIdentityProviders;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
index 023bd01..cc4b3e6 100644
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
@@ -30,6 +30,7 @@ public class IdentityProviderRepresentation {
     protected String name;
     protected boolean enabled = true;
     protected boolean updateProfileFirstLogin = true;
+    protected boolean storeToken;
     protected String groupName;
     protected Map<String, String> config = new HashMap<String, String>();
 
@@ -65,14 +66,6 @@ public class IdentityProviderRepresentation {
         this.config = config;
     }
 
-    public String getGroupName() {
-        return this.groupName;
-    }
-
-    public void setGroupName(String groupName) {
-        this.groupName = groupName;
-    }
-
     public boolean isEnabled() {
         return this.enabled;
     }
@@ -88,4 +81,20 @@ public class IdentityProviderRepresentation {
     public void setUpdateProfileFirstLogin(boolean updateProfileFirstLogin) {
         this.updateProfileFirstLogin = updateProfileFirstLogin;
     }
+
+    public boolean isStoreToken() {
+        return this.storeToken;
+    }
+
+    public void setStoreToken(boolean storeToken) {
+        this.storeToken = storeToken;
+    }
+
+    public String getGroupName() {
+        return this.groupName;
+    }
+
+    public void setGroupName(String groupName) {
+        this.groupName = groupName;
+    }
 }
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 de65ae6..12ce2f4 100755
--- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
@@ -22,6 +22,7 @@ public class OAuthClientRepresentation {
     protected Boolean directGrantsOnly;
     protected Boolean fullScopeAllowed;
     protected Boolean frontchannelLogout;
+    protected List<String> allowedIdentityProviders;
 
 
     public String getId() {
@@ -135,4 +136,12 @@ public class OAuthClientRepresentation {
     public void setFrontchannelLogout(Boolean frontchannelLogout) {
         this.frontchannelLogout = frontchannelLogout;
     }
+
+    public List<String> getAllowedIdentityProviders() {
+        return this.allowedIdentityProviders;
+    }
+
+    public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
+        this.allowedIdentityProviders = allowedIdentityProviders;
+    }
 }
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 d3cfbaf..13db021 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
@@ -464,6 +464,18 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ApplicationCredentialsCtrl'
         })
+        .when('/realms/:realm/applications/:application/identity-provider', {
+            templateUrl : 'partials/application-identity-provider.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                application : function(ApplicationLoader) {
+                    return ApplicationLoader();
+                }
+            },
+            controller : 'ApplicationIdentityProviderCtrl'
+        })
         .when('/realms/:realm/applications/:application/clustering', {
             templateUrl : 'partials/application-clustering.html',
             resolve : {
@@ -750,6 +762,18 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'OAuthClientDetailCtrl'
         })
+        .when('/realms/:realm/oauth-clients/:oauth/identity-provider', {
+            templateUrl : 'partials/oauth-client-identity-provider.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                oauth : function(OAuthClientLoader) {
+                    return OAuthClientLoader();
+                }
+            },
+            controller : 'OAuthClientIdentityProviderCtrl'
+        })
         .when('/realms/:realm/oauth-clients', {
             templateUrl : 'partials/oauth-client-list.html',
             resolve : {
@@ -1050,6 +1074,48 @@ module.directive('onoffswitch', function() {
     }
 });
 
+/**
+ * Directive for presenting an ON-OFF switch for checkbox.
+ * This directive provides some additional capabilities to the default onoffswitch such as:
+ *
+ * - Dynamic values for id and name attributes. Useful if you need to use this directive inside a ng-repeat
+ * - Specific scope to specify the value. Instead of just true or false.
+ *
+ * Usage: <input ng-model="mmm" name="nnn" id="iii" kc-onoffswitch-model [on-text="ooo" off-text="fff"] />
+ */
+module.directive('kc-onoffswitch-model', function() {
+    return {
+        restrict: "EA",
+        replace: true,
+        scope: {
+            name: '=',
+            id: '=',
+            value: '=',
+            ngModel: '=',
+            ngDisabled: '=',
+            kcOnText: '@onText',
+            kcOffText: '@offText'
+        },
+        // TODO - The same code acts differently when put into the templateURL. Find why and move the code there.
+        //templateUrl: "templates/kc-switch.html",
+        template: "<span><div class='onoffswitch' tabindex='0'><input type='checkbox' ng-true-value='{{value}}' ng-model='ngModel' ng-disabled='ngDisabled' class='onoffswitch-checkbox' name='kc{{name}}' id='kc{{id}}'><label for='kc{{id}}' class='onoffswitch-label'><span class='onoffswitch-inner'><span class='onoffswitch-active'>{{kcOnText}}</span><span class='onoffswitch-inactive'>{{kcOffText}}</span></span><span class='onoffswitch-switch'></span></label></div></span>",
+        compile: function(element, attrs) {
+
+            if (!attrs.onText) { attrs.onText = "ON"; }
+            if (!attrs.offText) { attrs.offText = "OFF"; }
+
+            element.bind('keydown click', function(e){
+                var code = e.keyCode || e.which;
+                if (code === 32 || code === 13) {
+                    e.stopImmediatePropagation();
+                    e.preventDefault();
+                    $(e.target).find('input').click();
+                }
+            });
+        }
+    }
+});
+
 module.directive('kcInput', function() {
     var d = {
         scope : true,
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 784f1b8..3f3a6cb 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
@@ -43,6 +43,77 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real
     });
 });
 
+module.controller('ApplicationIdentityProviderCtrl', function($scope, $location, realm, application, Application, $location, Notifications) {
+    $scope.realm = realm;
+    $scope.application = angular.copy(application);
+
+    $scope.identityProviders = [];
+
+    if (!$scope.application.allowedIdentityProviders) {
+        $scope.application.allowedIdentityProviders = [];
+    }
+
+    for (j = 0; j < realm.identityProviders.length; j++) {
+        var identityProvider = realm.identityProviders[j];
+        var match = false;
+
+        for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) {
+            var appProvider = $scope.application.allowedIdentityProviders[i];
+
+            if (appProvider == identityProvider.id) {
+                $scope.identityProviders[i] = identityProvider;
+                match = true;
+            }
+        }
+
+        if (!match) {
+            var length = $scope.identityProviders.length;
+
+            length = length + $scope.application.allowedIdentityProviders.length;
+
+            $scope.identityProviders[length] = identityProvider;
+        }
+    }
+
+    $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined });
+
+    $scope.save = function() {
+        var selectedProviders = [];
+
+        for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) {
+            var appProvider = $scope.application.allowedIdentityProviders[i];
+
+            if (appProvider) {
+                selectedProviders[selectedProviders.length] = appProvider;
+            }
+        }
+
+        $scope.allowedIdentityProviders = $scope.application.allowedIdentityProviders;
+        $scope.application.allowedIdentityProviders = 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");
+            Notifications.success("Your changes have been saved to the application.");
+        });
+    };
+
+    $scope.reset = function() {
+        $scope.application = angular.copy(application);
+        $scope.changed = false;
+    };
+
+    $scope.$watch(function() {
+        return $location.path();
+    }, function() {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+});
+
 module.controller('ApplicationSamlKeyCtrl', function($scope, $location, $http, $upload, realm, application,
                                                          ApplicationCertificate, ApplicationCertificateGenerate,
                                                          ApplicationCertificateDownload, Notifications) {
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 5fe70a7..dec42f5 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,4 +324,74 @@ module.controller('OAuthClientRevocationCtrl', function($scope, realm, oauth, OA
     }
 });
 
+module.controller('OAuthClientIdentityProviderCtrl', function($scope, realm, oauth, OAuthClient, $location, Notifications) {
+    $scope.realm = realm;
+    $scope.oauth = angular.copy(oauth);
+
+    $scope.identityProviders = [];
+
+    if (!$scope.oauth.allowedIdentityProviders) {
+        $scope.oauth.allowedIdentityProviders = [];
+    }
+
+    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;
+            }
+        }
+
+        if (!match) {
+            var length = $scope.identityProviders.length;
+
+            length = length + $scope.oauth.allowedIdentityProviders.length;
+
+            $scope.identityProviders[length] = identityProvider;
+        }
+    }
+
+    $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined });
+
+    $scope.save = function() {
+        var selectedProviders = [];
+
+        for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) {
+            var appProvider = $scope.oauth.allowedIdentityProviders[i];
+
+            if (appProvider) {
+                selectedProviders[selectedProviders.length] = appProvider;
+            }
+        }
+
+        $scope.allowedIdentityProviders = $scope.oauth.allowedIdentityProviders;
+        $scope.oauth.allowedIdentityProviders = 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");
+            Notifications.success("Your changes have been saved to the application.");
+        });
+    };
+
+    $scope.reset = function() {
+        $scope.oauth = angular.copy(oauth);
+        $scope.changed = false;
+    };
+
+    $scope.$watch(function() {
+        return $location.path();
+    }, function() {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+});
 
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
new file mode 100755
index 0000000..6c90ef2
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html
@@ -0,0 +1,24 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+    <kc-navigation-application></kc-navigation-application>
+    <div id="content">
+        <ol class="breadcrumb" data-ng-hide="create">
+            <li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
+            <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</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>
+                <div class="col-sm-4">
+                    <input ng-model="application.allowedIdentityProviders[$index]" name="identityProvider.id" id="identityProvider.id" value="identityProvider.id" kc-onoffswitch-model />
+                </div>
+            </div>
+            <div class="pull-right form-actions">
+                <button kc-save>Save</button>
+            </div>
+        </form>
+    </div>
+</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
new file mode 100755
index 0000000..1a14620
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html
@@ -0,0 +1,24 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+    <kc-navigation-oauth-client></kc-navigation-oauth-client>
+    <div id="content">
+        <ol class="breadcrumb" data-ng-hide="create">
+            <li><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
+            <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">{{oauth.name}}</a></li>
+            <li class="active">Identity Provider</li>
+        </ol>
+        <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>
+                <div class="col-sm-4">
+                    <input ng-model="oauth.allowedIdentityProviders[$index]" name="identityProvider.id" id="identityProvider.id" value="identityProvider.id" kc-onoffswitch-model />
+                </div>
+            </div>
+            <div class="pull-right form-actions">
+                <button kc-save>Save</button>
+            </div>
+        </form>
+    </div>
+</div>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html
index 135c392..1e4de59 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html
@@ -93,6 +93,13 @@
                         <span tooltip-placement="right" tooltip="Specifies whether the Authorization Server prompts the End-User for reauthentication and consent." class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group">
+                        <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Enable/disable if tokens must be stored when authenticating users." class="fa fa-info-circle"></span>
+                    </div>
+                    <div class="form-group">
                         <label class="col-sm-2 control-label" for="enabled">Enabled</label>
                         <div class="col-sm-4">
                             <input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
index e5ba81f..91cf006 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
@@ -94,6 +94,13 @@
                         <span tooltip-placement="right" tooltip="Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used." class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group">
+                        <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Enable/disable if tokens must be stored when authenticating users." class="fa fa-info-circle"></span>
+                    </div>
+                    <div class="form-group">
                         <label class="col-sm-2 control-label" for="enabled">Enabled</label>
                         <div class="col-sm-4">
                             <input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html
index 0cd6816..e5a28e1 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html
@@ -50,6 +50,13 @@
                         <span tooltip-placement="right" tooltip="The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'." class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group">
+                        <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Enable/disable if tokens must be stored when authenticating users." class="fa fa-info-circle"></span>
+                    </div>
+                    <div class="form-group">
                         <label class="col-sm-2 control-label" for="enabled">Enabled</label>
                         <div class="col-sm-4">
                             <input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
index cb4d44a..e84e149 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
@@ -6,6 +6,7 @@
     <li ng-class="{active: path[4] == 'claims'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/claims">Claims</a></li>
     <li ng-class="{active: path[4] == 'scope-mappings'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
     <li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/revocation">Revocation</a></li>
+    <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/identity-provider">Identity Provider</a></li>
     <li ng-class="{active: path[4] == 'sessions'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/sessions">Sessions</a></li>
     <li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/clustering">Clustering</a></li>
     <li ng-class="{active: path[4] == 'installation'}" data-ng-show="application.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/installation">Installation</a></li>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html
index ffca271..db40ee5 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html
@@ -4,5 +4,6 @@
     <li ng-class="{active: path[4] == 'claims'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
     <li ng-class="{active: path[4] == 'scope-mappings'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
     <li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/revocation">Revocation</a></li>
+    <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/identity-provider">Identity Provider</a></li>
     <li ng-class="{active: path[4] == 'installation'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
 </ul>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
index e8cfbf3..c881e9c 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
@@ -14,25 +14,35 @@
                         <span>
                         Personal Info:&nbsp;
                             <#list oauth.claimsRequested as claim>
-                                ${claim}&nbsp;
+                            ${claim}&nbsp;
                             </#list>
                         </span>
                     </li>
                 </#if>
-                <#list oauth.realmRolesRequested as role>
+                <#if oauth.accessRequestMessage??>
                     <li>
-                        <span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
+                        <span>
+                            ${oauth.accessRequestMessage}
+                        </span>
                     </li>
-                </#list>
-
-                <#list oauth.resourceRolesRequested?keys as resource>
-                    <#list oauth.resourceRolesRequested[resource] as role>
+                </#if>
+                <#if oauth.realmRolesRequested??>
+                    <#list oauth.realmRolesRequested as role>
                         <li>
-                            <span class="kc-role"><#if role.description??>${role.description}<#else>${role.name}</#if></span>
-                            <span class="kc-resource">in <strong>${resource}</strong></span>
+                            <span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
                         </li>
                     </#list>
-                </#list>
+                </#if>
+                <#if oauth.resourceRolesRequested??>
+                    <#list oauth.resourceRolesRequested?keys as resource>
+                        <#list oauth.resourceRolesRequested[resource] as role>
+                            <li>
+                                <span class="kc-role"><#if role.description??>${role.description}<#else>${role.name}</#if></span>
+                                <span class="kc-resource">in <strong>${resource}</strong></span>
+                            </li>
+                        </#list>
+                    </#list>
+                </#if>
             </ul>
 
             <form class="form-actions" action="${url.oauthAction}" method="POST">
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 4fd2de9..0d1f931 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -9,6 +9,7 @@ import org.keycloak.provider.Provider;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import java.net.URI;
 import java.util.List;
 
 /**
@@ -39,6 +40,7 @@ public interface LoginFormsProvider extends Provider {
     public LoginFormsProvider setClientSessionCode(String accessCode);
 
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
+    public LoginFormsProvider setAccessRequest(String message);
 
     public LoginFormsProvider setError(String message);
 
@@ -56,4 +58,5 @@ public interface LoginFormsProvider extends Provider {
 
     public LoginFormsProvider setStatus(Response.Status status);
 
+    LoginFormsProvider setActionUri(URI requestUri);
 }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 704f8a7..f897a5a 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -57,6 +57,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     private List<RoleModel> realmRolesRequested;
     private MultivaluedMap<String, RoleModel> resourceRolesRequested;
     private MultivaluedMap<String, String> queryParams;
+    private String accessRequestMessage;
+    private URI actionUri;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
@@ -188,8 +190,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         if (realm != null) {
             attributes.put("realm", new RealmBean(realm));
-            attributes.put("social", new IdentityProviderBean(realm, baseUri));
-            attributes.put("url", new UrlBean(realm, theme, baseUri));
+            attributes.put("social", new IdentityProviderBean(realm, baseUri, this.uriInfo));
+            attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
         }
 
         if (client != null) {
@@ -209,7 +211,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 attributes.put("register", new RegisterBean(formData));
                 break;
             case OAUTH_GRANT:
-                attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested));
+                attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested, this.accessRequestMessage));
                 break;
             case CODE:
                 attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? message : null));
@@ -304,6 +306,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public LoginFormsProvider setAccessRequest(String accessRequestMessage) {
+        this.accessRequestMessage = accessRequestMessage;
+        return this;
+    }
+
+    @Override
     public LoginFormsProvider setStatus(Response.Status status) {
         this.status = status;
         return this;
@@ -316,6 +324,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public LoginFormsProvider setActionUri(URI actionUri) {
+        this.actionUri = actionUri;
+        return this;
+    }
+
+    @Override
     public void close() {
     }
 
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
index 80f29bb..5a77a1a 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
@@ -21,10 +21,13 @@
  */
 package org.keycloak.login.freemarker.model;
 
+import org.keycloak.OAuth2Constants;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.resources.flows.Urls;
 
+import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 import java.util.LinkedList;
 import java.util.List;
@@ -39,7 +42,7 @@ public class IdentityProviderBean {
     private List<IdentityProvider> providers;
     private RealmModel realm;
 
-    public IdentityProviderBean(RealmModel realm, URI baseURI) {
+    public IdentityProviderBean(RealmModel realm, URI baseURI, UriInfo uriInfo) {
         this.realm = realm;
         List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
 
@@ -48,6 +51,16 @@ public class IdentityProviderBean {
 
             for (IdentityProviderModel identityProvider : identityProviders) {
                 if (identityProvider.isEnabled()) {
+                    String clientId = uriInfo.getQueryParameters().getFirst(OAuth2Constants.CLIENT_ID);
+
+                    if (clientId != null) {
+                        ClientModel clientModel = realm.findClient(clientId);
+
+                        if (clientModel != null && !clientModel.hasIdentityProvider(identityProvider.getId())) {
+                            continue;
+                        }
+                    }
+
                     String loginUrl = Urls.identityProviderAuthnRequest(baseURI, identityProvider, realm).toString();
                     providers.add(new IdentityProvider(identityProvider.getId(), identityProvider.getName(), loginUrl));
                 }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java
index f358d3d..6d515a1 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java
@@ -34,17 +34,19 @@ import java.util.List;
  */
 public class OAuthGrantBean {
 
+    private final String accessRequestMessage;
     private List<RoleModel> realmRolesRequested;
     private MultivaluedMap<String, RoleModel> resourceRolesRequested;
     private String code;
     private ClientModel client;
     private List<String> claimsRequested;
 
-    public OAuthGrantBean(String code, ClientModel client, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
+    public OAuthGrantBean(String code, ClientModel client, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, String accessRequestMessage) {
         this.code = code;
         this.client = client;
         this.realmRolesRequested = realmRolesRequested;
         this.resourceRolesRequested = resourceRolesRequested;
+        this.accessRequestMessage = accessRequestMessage;
 
         // todo support locale
         List<String> claims = new LinkedList<String>();
@@ -101,4 +103,8 @@ public class OAuthGrantBean {
     public List<String> getClaimsRequested() {
         return claimsRequested;
     }
+
+    public String getAccessRequestMessage() {
+        return this.accessRequestMessage;
+    }
 }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index bb8df0e..26d67a3 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -32,16 +32,18 @@ import java.net.URI;
  */
 public class UrlBean {
 
+    private final URI actionuri;
     private URI baseURI;
 
     private Theme theme;
 
     private String realm;
 
-    public UrlBean(RealmModel realm, Theme theme, URI baseURI) {
+    public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI actionUri) {
         this.realm = realm.getName();
         this.theme = theme;
         this.baseURI = baseURI;
+        this.actionuri = actionUri;
     }
 
     public String getLoginAction() {
@@ -85,6 +87,10 @@ public class UrlBean {
     }
 
     public String getOauthAction() {
+        if (this.actionuri != null) {
+            return this.actionuri.getPath();
+        }
+
         return Urls.realmOauthAction(baseURI, realm).toString();
     }
 
@@ -92,5 +98,4 @@ public class UrlBean {
         URI uri = Urls.themeRoot(baseURI);
         return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
     }
-
 }
diff --git a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
index 14cd081..09f2a55 100755
--- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
@@ -53,5 +53,4 @@ public interface ApplicationModel extends RoleContainerModel, ClientModel {
     void registerNode(String nodeHost, int registrationTime);
 
     void unregisterNode(String nodeHost);
-
 }
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 ecbb489..0acd9c5 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -1,5 +1,6 @@
 package org.keycloak.models;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -97,4 +98,9 @@ public interface ClientModel {
 
     void setNotBefore(int notBefore);
 
+    void updateAllowedIdentityProviders(List<String> identityProviders);
+
+    List<String> getAllowedIdentityProviders();
+
+    boolean hasIdentityProvider(String providerId);
 }
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 5b37c34..9cfcead 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
@@ -27,6 +27,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;
 
     public String getName() {
         return name;
@@ -139,4 +140,12 @@ public class ClientEntity extends AbstractIdentifiableEntity {
     public void setFrontchannelLogout(boolean frontchannelLogout) {
         this.frontchannelLogout = frontchannelLogout;
     }
+
+    public List<String> getAllowedIdentityProviders() {
+        return this.allowedIdentityProviders;
+    }
+
+    public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
+        this.allowedIdentityProviders = allowedIdentityProviders;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java b/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java
index a8fa647..02cdfac 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java
@@ -8,6 +8,7 @@ public class FederatedIdentityEntity {
     private String userId;
     private String userName;
     private String identityProvider;
+    private String token;
 
     public String getUserId() {
         return userId;
@@ -59,4 +60,12 @@ public class FederatedIdentityEntity {
         }
         return code;
     }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getToken() {
+        return token;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
index 964a12c..4fb33f2 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
@@ -26,11 +26,13 @@ import java.util.Map;
 public class IdentityProviderEntity {
 
     private String internalId;
+    private String id;
+    private String providerId;
     private String name;
     private boolean enabled;
     private boolean updateProfileFirstLogin;
-    private String providerId;
-    private String id;
+    private boolean storeToken;
+
     private Map<String, String> config = new HashMap<String, String>();
 
     public String getInternalId() {
@@ -65,6 +67,14 @@ public class IdentityProviderEntity {
         this.updateProfileFirstLogin = updateProfileFirstLogin;
     }
 
+    public boolean isStoreToken() {
+        return this.storeToken;
+    }
+
+    public void setStoreToken(boolean storeToken) {
+        this.storeToken = storeToken;
+    }
+
     public String getProviderId() {
         return providerId;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java b/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java
index 77db720..e720c40 100755
--- a/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java
+++ b/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java
@@ -5,16 +5,20 @@ package org.keycloak.models;
  */
 public class FederatedIdentityModel {
 
-    private String userId;
-    private String identityProvider;
-    private String userName;
-
-    public FederatedIdentityModel() {};
+    private final String token;
+    private final String userId;
+    private final String identityProvider;
+    private final String userName;
 
     public FederatedIdentityModel(String identityProvider, String userId, String userName) {
+        this(identityProvider, userId, userName, null);
+    }
+
+    public FederatedIdentityModel(String providerId, String userId, String userName, String token) {
+        this.identityProvider = providerId;
         this.userId = userId;
-        this.identityProvider = identityProvider;
         this.userName = userName;
+        this.token = token;
     }
 
     public String getUserId() {
@@ -28,4 +32,8 @@ public class FederatedIdentityModel {
     public String getUserName() {
         return userName;
     }
+
+    public String getToken() {
+        return this.token;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
index 11a95a2..76ca2d8 100644
--- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -50,6 +50,8 @@ public class IdentityProviderModel {
 
     private boolean updateProfileFirstLogin = true;
 
+    private boolean storeToken;
+
     /**
      * <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
      * in the map are understood by the identity provider implementation.</p>
@@ -67,6 +69,7 @@ public class IdentityProviderModel {
         this.config = new HashMap<String, String>(model.getConfig());
         this.enabled = model.isEnabled();
         this.updateProfileFirstLogin = model.isUpdateProfileFirstLogin();
+        this.storeToken = model.isStoreToken();
     }
 
     public String getInternalId() {
@@ -117,6 +120,14 @@ public class IdentityProviderModel {
         this.updateProfileFirstLogin = updateProfileFirstLogin;
     }
 
+    public boolean isStoreToken() {
+        return this.storeToken;
+    }
+
+    public void setStoreToken(boolean storeToken) {
+        this.storeToken = storeToken;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
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 42c4423..8fe9688 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
@@ -258,6 +258,10 @@ public class ModelToRepresentation {
             rep.setRegisteredNodes(new HashMap<String, Integer>(applicationModel.getRegisteredNodes()));
         }
 
+        if (!applicationModel.getAllowedIdentityProviders().isEmpty()) {
+            rep.setAllowedIdentityProviders(applicationModel.getAllowedIdentityProviders());
+        }
+
         return rep;
     }
 
@@ -282,6 +286,11 @@ public class ModelToRepresentation {
             rep.setWebOrigins(new LinkedList<String>(webOrigins));
         }
         rep.setNotBefore(model.getNotBefore());
+
+        if (!model.getAllowedIdentityProviders().isEmpty()) {
+            rep.setAllowedIdentityProviders(model.getAllowedIdentityProviders());
+        }
+
         return rep;
     }
 
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 aede6a2..0c7835c 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
@@ -112,6 +112,8 @@ public class RepresentationToModel {
 
         if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
 
+        importIdentityProviders(rep, newRealm);
+
         if (rep.getApplications() != null) {
             Map<String, ApplicationModel> appMap = createApplications(rep, newRealm);
         }
@@ -231,21 +233,6 @@ public class RepresentationToModel {
                 UserModel user = createUser(session, newRealm, userRep, appMap);
             }
         }
-
-        if (rep.getIdentityProviders() != null) {
-            for (IdentityProviderRepresentation identityProviderRepresentation : rep.getIdentityProviders()) {
-                IdentityProviderModel identityProviderModel = new IdentityProviderModel();
-
-                identityProviderModel.setId(identityProviderRepresentation.getId());
-                identityProviderModel.setProviderId(identityProviderRepresentation.getProviderId());
-                identityProviderModel.setName(identityProviderRepresentation.getName());
-                identityProviderModel.setEnabled(identityProviderRepresentation.isEnabled());
-                identityProviderModel.setUpdateProfileFirstLogin(identityProviderRepresentation.isUpdateProfileFirstLogin());
-                identityProviderModel.setConfig(identityProviderRepresentation.getConfig());
-
-                newRealm.addIdentityProvider(identityProviderModel);
-            }
-        }
     }
 
     public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
@@ -517,6 +504,10 @@ public class RepresentationToModel {
         if (rep.getClaims() != null) {
             setClaims(resource, rep.getClaims());
         }
+
+        if (rep.getAllowedIdentityProviders() != null) {
+            resource.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders());
+        }
     }
 
     public static void setClaims(ClientModel model, ClaimRepresentation rep) {
@@ -632,6 +623,9 @@ public class RepresentationToModel {
             }
         }
 
+        if (rep.getAllowedIdentityProviders() != null) {
+            model.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders());
+        }
     }
 
     // Scope mappings
@@ -747,4 +741,22 @@ public class RepresentationToModel {
 
         }
     }
+
+    private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) {
+        if (rep.getIdentityProviders() != null) {
+            for (IdentityProviderRepresentation identityProviderRepresentation : rep.getIdentityProviders()) {
+                IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+                identityProviderModel.setId(identityProviderRepresentation.getId());
+                identityProviderModel.setProviderId(identityProviderRepresentation.getProviderId());
+                identityProviderModel.setName(identityProviderRepresentation.getName());
+                identityProviderModel.setEnabled(identityProviderRepresentation.isEnabled());
+                identityProviderModel.setUpdateProfileFirstLogin(identityProviderRepresentation.isUpdateProfileFirstLogin());
+                identityProviderModel.setStoreToken(identityProviderRepresentation.isStoreToken());
+                identityProviderModel.setConfig(identityProviderRepresentation.getConfig());
+
+                newRealm.addIdentityProvider(identityProviderModel);
+            }
+        }
+    }
 }
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 dff08a6..11696c5 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
@@ -8,6 +8,7 @@ import org.keycloak.models.cache.entities.CachedClient;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -259,4 +260,22 @@ public abstract class ClientAdapter implements ClientModel {
         copy.putAll(cachedClient.getAttributes());
         return copy;
     }
+
+    @Override
+    public void updateAllowedIdentityProviders(List<String> identityProviders) {
+        getDelegateForUpdate();
+        updatedClient.updateAllowedIdentityProviders(identityProviders);
+    }
+
+    @Override
+    public List<String> getAllowedIdentityProviders() {
+        if (updatedClient != null) return updatedClient.getAllowedIdentityProviders();
+        return cachedClient.getAllowedIdentityProviders();
+    }
+
+    @Override
+    public boolean hasIdentityProvider(String providerId) {
+        if (updatedClient != null) return updatedClient.hasIdentityProvider(providerId);
+        return cachedClient.hasIdentityProvider(providerId);
+    }
 }
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 b4b605e..ac7341d 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
@@ -6,8 +6,10 @@ import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.cache.RealmCache;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -32,6 +34,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>();
 
     public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
         id = model.getId();
@@ -52,7 +55,7 @@ public class CachedClient {
         for (RoleModel role : model.getScopeMappings())  {
             scope.add(role.getId());
         }
-
+        this.allowedIdentityProviders = model.getAllowedIdentityProviders();
     }
 
     public String getId() {
@@ -122,4 +125,16 @@ public class CachedClient {
     public void setFrontchannelLogout(boolean frontchannelLogout) {
         this.frontchannelLogout = frontchannelLogout;
     }
+
+    public List<String> getAllowedIdentityProviders() {
+        return this.allowedIdentityProviders;
+    }
+
+    public boolean hasIdentityProvider(String providerId) {
+        if (this.allowedIdentityProviders.isEmpty()) {
+            return true;
+        }
+
+        return this.allowedIdentityProviders.contains(providerId);
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 9eab924..dd1603c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -7,9 +7,9 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.jpa.entities.ApplicationEntity;
+import org.keycloak.models.jpa.entities.IdentityProviderEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.util.Time;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
@@ -231,13 +231,6 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
         em.flush();
     }
 
-    public static boolean contains(String str, String[] array) {
-        for (String s : array) {
-            if (str.equals(s)) return true;
-        }
-        return false;
-    }
-
     @Override
     public void updateDefaultRoles(String[] defaultRoles) {
         Collection<RoleEntity> entities = applicationEntity.getDefaultRoles();
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 e71ba14..e8fb943 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
@@ -5,11 +5,14 @@ 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.IdentityProviderEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.jpa.entities.ScopeMappingEntity;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -293,4 +296,60 @@ public abstract class ClientAdapter implements ClientModel {
         copy.putAll(entity.getAttributes());
         return copy;
     }
+
+    @Override
+    public void updateAllowedIdentityProviders(List<String> identityProviders) {
+        Collection<IdentityProviderEntity> entities = entity.getAllowedIdentityProviders();
+        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);
+            } else {
+                already.add(rel.getId());
+            }
+        }
+        for (IdentityProviderEntity entity : remove) {
+            entities.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);
+            }
+        }
+        em.flush();
+    }
+
+    @Override
+    public List<String> getAllowedIdentityProviders() {
+        Collection<IdentityProviderEntity> entities = entity.getAllowedIdentityProviders();
+        List<String> providers = new ArrayList<String>();
+
+        for (IdentityProviderEntity entity : entities) {
+            providers.add(entity.getId());
+        }
+
+        return providers;
+    }
+
+    @Override
+    public boolean hasIdentityProvider(String providerId) {
+        List<String> allowedIdentityProviders = getAllowedIdentityProviders();
+
+        if (allowedIdentityProviders.isEmpty()) {
+            return true;
+        }
+
+        return allowedIdentityProviders.contains(providerId);
+    }
+
+    public static boolean contains(String str, String[] array) {
+        for (String s : array) {
+            if (str.equals(s)) return true;
+        }
+        return false;
+    }
 }
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 8b32096..ac43656 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
@@ -9,10 +9,14 @@ import javax.persistence.Id;
 import javax.persistence.Inheritance;
 import javax.persistence.InheritanceType;
 import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
 import javax.persistence.ManyToOne;
 import javax.persistence.MapKeyColumn;
+import javax.persistence.OneToMany;
 import javax.persistence.Table;
 import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -68,6 +72,10 @@ 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>();
+
     public RealmEntity getRealm() {
         return realm;
     }
@@ -179,4 +187,12 @@ public abstract class ClientEntity {
     public void setFrontchannelLogout(boolean frontchannelLogout) {
         this.frontchannelLogout = frontchannelLogout;
     }
+
+    public Collection<IdentityProviderEntity> getAllowedIdentityProviders() {
+        return this.allowedIdentityProviders;
+    }
+
+    public void setAllowedIdentityProviders(Collection<IdentityProviderEntity> allowedIdentityProviders) {
+        this.allowedIdentityProviders = allowedIdentityProviders;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java
index 1a91888..ccb1465 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java
@@ -45,6 +45,9 @@ public class FederatedIdentityEntity {
     @Column(name = "FEDERATED_USERNAME")
     protected String userName;
 
+    @Column(name = "TOKEN")
+    protected String token;
+
     public UserEntity getUser() {
         return user;
     }
@@ -85,6 +88,14 @@ public class FederatedIdentityEntity {
         this.realmId = realmId;
     }
 
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
     public static class Key implements Serializable {
 
         protected UserEntity user;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
index 96d9c50..408d706 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
@@ -9,6 +9,8 @@ import javax.persistence.Id;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
 import javax.persistence.Table;
 import java.util.Map;
 
@@ -17,6 +19,9 @@ import java.util.Map;
  */
 @Entity
 @Table(name="IDENTITY_PROVIDER")
+@NamedQueries({
+        @NamedQuery(name="findIdentityProviderById", query="select identityProvider from IdentityProviderEntity identityProvider where identityProvider.id = :id")
+})
 public class IdentityProviderEntity {
 
     @Id
@@ -42,6 +47,9 @@ public class IdentityProviderEntity {
     @Column(name="UPDATE_PROFILE_FIRST_LOGIN")
     private boolean updateProfileFirstLogin;
 
+    @Column(name="STORE_TOKEN")
+    private boolean storeToken;
+
     @ElementCollection
     @MapKeyColumn(name="name")
     @Column(name="value", columnDefinition = "TEXT")
@@ -104,6 +112,14 @@ public class IdentityProviderEntity {
         this.updateProfileFirstLogin = updateProfileFirstLogin;
     }
 
+    public boolean isStoreToken() {
+        return this.storeToken;
+    }
+
+    public void setStoreToken(boolean storeToken) {
+        this.storeToken = storeToken;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 60a79e6..098421a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -96,6 +96,7 @@ public class JpaUserProvider implements UserProvider {
         entity.setIdentityProvider(identity.getIdentityProvider());
         entity.setUserId(identity.getUserId());
         entity.setUserName(identity.getUserName());
+        entity.setToken(identity.getToken());
         UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
         entity.setUser(userEntity);
         em.persist(entity);
@@ -344,7 +345,7 @@ public class JpaUserProvider implements UserProvider {
         List<FederatedIdentityEntity> results = query.getResultList();
         Set<FederatedIdentityModel> set = new HashSet<FederatedIdentityModel>();
         for (FederatedIdentityEntity entity : results) {
-            set.add(new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName()));
+            set.add(new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()));
         }
         return set;
     }
@@ -352,7 +353,7 @@ public class JpaUserProvider implements UserProvider {
     @Override
     public FederatedIdentityModel getFederatedIdentity(UserModel user, String identityProvider, RealmModel realm) {
         FederatedIdentityEntity entity = findFederatedIdentity(user, identityProvider);
-        return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName()) : null;
+        return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()) : null;
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 6cd1d0a..9f2b93b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1120,6 +1120,7 @@ public class RealmAdapter implements RealmModel {
             identityProviderModel.setConfig(entity.getConfig());
             identityProviderModel.setEnabled(entity.isEnabled());
             identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
+            identityProviderModel.setStoreToken(entity.isStoreToken());
 
             identityProviders.add(identityProviderModel);
         }
@@ -1174,6 +1175,7 @@ public class RealmAdapter implements RealmModel {
                 entity.setName(identityProvider.getName());
                 entity.setEnabled(identityProvider.isEnabled());
                 entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+                entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
             }
         }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index 3f84c2b..02e512a 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -11,7 +11,6 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
-import org.keycloak.util.Time;
 
 import java.util.ArrayList;
 import java.util.Collections;
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 ad56f84..d3f93db 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
@@ -291,5 +291,30 @@ public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends A
         return copy;
     }
 
+    @Override
+    public void updateAllowedIdentityProviders(List<String> identityProviders) {
+        List<String> providerIds = new ArrayList<String>();
+        for (String providerId : identityProviders) {
+            providerIds.add(providerId);
+        }
 
+        getMongoEntityAsClient().setAllowedIdentityProviders(identityProviders);
+        updateMongoEntity();
+    }
+
+    @Override
+    public List<String> getAllowedIdentityProviders() {
+        return getMongoEntityAsClient().getAllowedIdentityProviders();
+    }
+
+    @Override
+    public boolean hasIdentityProvider(String providerId) {
+        List<String> allowedIdentityProviders = getMongoEntityAsClient().getAllowedIdentityProviders();
+
+        if (allowedIdentityProviders.isEmpty()) {
+            return true;
+        }
+
+        return allowedIdentityProviders.contains(providerId);
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index a542c22..b51fce3 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -6,10 +6,10 @@ import com.mongodb.QueryBuilder;
 import org.keycloak.connections.mongo.api.MongoStore;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
-import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
@@ -225,7 +225,7 @@ public class MongoUserProvider implements UserProvider {
         Set<FederatedIdentityModel> result = new HashSet<FederatedIdentityModel>();
         for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
             FederatedIdentityModel model = new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(),
-                    federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName());
+                    federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName(), federatedIdentityEntity.getToken());
             result.add(model);
         }
         return result;
@@ -296,13 +296,14 @@ public class MongoUserProvider implements UserProvider {
 
 
     @Override
-    public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
+    public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) {
         user = getUserById(user.getId(), realm);
         MongoUserEntity userEntity = ((UserAdapter) user).getUser();
         FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
-        federatedIdentityEntity.setIdentityProvider(socialLink.getIdentityProvider());
-        federatedIdentityEntity.setUserId(socialLink.getUserId());
-        federatedIdentityEntity.setUserName(socialLink.getUserName());
+        federatedIdentityEntity.setIdentityProvider(identity.getIdentityProvider());
+        federatedIdentityEntity.setUserId(identity.getUserId());
+        federatedIdentityEntity.setUserName(identity.getUserName());
+        federatedIdentityEntity.setToken(identity.getToken());
 
         getMongoStore().pushItemToList(userEntity, "federatedIdentities", federatedIdentityEntity, true, invocationContext);
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index de0a0cc..44058be 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -796,6 +796,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             identityProviderModel.setConfig(entity.getConfig());
             identityProviderModel.setEnabled(entity.isEnabled());
             identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
+            identityProviderModel.setStoreToken(entity.isStoreToken());
 
             identityProviders.add(identityProviderModel);
         }
@@ -850,6 +851,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
                 entity.setName(identityProvider.getName());
                 entity.setEnabled(identityProvider.isEnabled());
                 entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+                entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
             }
         }
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 fc0e6ca..1917844 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
@@ -5,6 +5,7 @@ import org.jboss.resteasy.plugins.providers.multipart.InputPart;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -83,6 +84,7 @@ public class IdentityProviderResource {
         String providerId = formDataMap.get("providerId").get(0).getBodyAsString();
         String enabled = formDataMap.get("enabled").get(0).getBodyAsString();
         String updateProfileFirstLogin = formDataMap.get("updateProfileFirstLogin").get(0).getBodyAsString();
+        String storeToken = formDataMap.get("storeToken").get(0).getBodyAsString();
         InputPart file = formDataMap.get("file").get(0);
         InputStream inputStream = file.getBody(InputStream.class, null);
         IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
@@ -94,6 +96,7 @@ public class IdentityProviderResource {
         providerModel.setProviderId(providerId);
         providerModel.setEnabled(Boolean.valueOf(enabled));
         providerModel.setUpdateProfileFirstLogin(Boolean.valueOf(updateProfileFirstLogin));
+        providerModel.setStoreToken(Boolean.valueOf(storeToken));
         providerModel.setConfig(config);
 
         return create(uriInfo, providerModel);
@@ -117,10 +120,32 @@ public class IdentityProviderResource {
     @DELETE
     @NoCache
     public Response delete(@PathParam("id") String providerId) {
+        for (ClientModel applicationModel : getClientModels()) {
+            List<String> allowedIdentityProviders = applicationModel.getAllowedIdentityProviders();
+
+            for (String appProvider : new ArrayList<String>(allowedIdentityProviders)) {
+                if (appProvider.equals(providerId)) {
+                    allowedIdentityProviders.remove(appProvider);
+                }
+            }
+
+            applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders);
+        }
+
         this.realm.removeIdentityProviderById(providerId);
+
         return Response.noContent().build();
     }
 
+    private List<ClientModel> getClientModels() {
+        List<ClientModel> clients = new ArrayList<ClientModel>();
+
+        clients.addAll(this.realm.getOAuthClients());
+        clients.addAll(this.realm.getApplications());
+
+        return clients;
+    }
+
     @PUT
     @Consumes("application/json")
     public Response update(IdentityProviderModel model) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
index 0f14b30..17c05e5 100644
--- a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
@@ -28,28 +28,35 @@ import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.provider.ProviderFactory;
+import org.keycloak.services.managers.AppAuthManager;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationManager.AuthResult;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.EventsManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.social.SocialIdentityProvider;
 
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
@@ -100,6 +107,13 @@ public class AuthenticationBrokerResource {
 
         try {
             ClientSessionModel clientSession = clientCode.getClientSession();
+            ClientModel clientModel = clientSession.getClient();
+            Response response = checkClientPermissions(clientModel, providerId);
+
+            if (response != null) {
+                return response;
+            }
+
             IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
 
             if (identityProvider == null) {
@@ -109,7 +123,8 @@ public class AuthenticationBrokerResource {
 
             AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, code, realm,
                     clientSession));
-            Response response = authenticationResponse.getResponse();
+
+            response = authenticationResponse.getResponse();
 
             if (response != null) {
                 event.success();
@@ -142,6 +157,67 @@ public class AuthenticationBrokerResource {
         return handleResponse(realmName, providerId);
     }
 
+    @GET
+    @Path("{realm}/{provider_id}/token")
+    public Response retrieveToken(@PathParam("realm") final String realmName, @PathParam("provider_id") String providerId) {
+        return getToken(realmName, providerId, false);
+    }
+
+    private Response getToken(String realmName, String providerId, boolean forceRetrieval) {
+        RealmManager realmManager = new RealmManager(session);
+        RealmModel realm = realmManager.getRealmByName(realmName);
+        AppAuthManager authManager = new AppAuthManager();
+        AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, request.getHttpHeaders());
+
+        if (authResult != null) {
+            String audience = authResult.getToken().getAudience();
+            ClientModel clientModel = realm.findClient(audience);
+
+            if (clientModel == null) {
+                return Flows.errors().error("Invalid client.", Response.Status.FORBIDDEN);
+            }
+
+            if (!clientModel.hasIdentityProvider(providerId)) {
+                return Flows.errors().error("Client [" + audience + "] not authorized.", Response.Status.FORBIDDEN);
+            }
+
+            if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) {
+                return Flows.forms(session, realm, clientModel, uriInfo)
+                        .setClientSessionCode(authManager.extractAuthorizationHeaderToken(request.getHttpHeaders()))
+                        .setAccessRequest("Your information from " + providerId + " identity provider.")
+                        .setClient(clientModel)
+                        .setUriInfo(this.uriInfo)
+                        .setActionUri(this.uriInfo.getRequestUri())
+                        .createOAuthGrant();
+            }
+
+            IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
+            IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
+
+            if (identityProviderConfig.isStoreToken()) {
+                FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, realm);
+
+                return identityProvider.retrieveToken(identity);
+            }
+
+            return Flows.errors().error("Identity Provider [" + providerId + "] does not support this operation.", Response.Status.FORBIDDEN);
+        }
+
+        return Flows.errors().error("Invalid code.", Response.Status.FORBIDDEN);
+    }
+
+    @POST
+    @Path("{realm}/{provider_id}/token")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response consentTokenRetrieval(@PathParam("realm") final String realmName, @PathParam("provider_id") String providerId,
+                                          final MultivaluedMap<String, String> formData) {
+        if (formData.containsKey("cancel")) {
+            return Flows.errors().error("Permission not approved.", Response.Status.FORBIDDEN);
+        }
+
+        return getToken(realmName, providerId, true);
+    }
+
     private Response handleResponse(String realmName, String providerId) {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = realmManager.getRealmByName(realmName);
@@ -166,17 +242,24 @@ public class AuthenticationBrokerResource {
             }
 
             ClientSessionModel clientSession = clientCode.getClientSession();
+            ClientModel clientModel = clientSession.getClient();
+            Response response = checkClientPermissions(clientModel, providerId);
+
+            if (response != null) {
+                return response;
+            }
 
             AuthenticationResponse authenticationResponse = provider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession));
-            Response response = authenticationResponse.getResponse();
+
+            response = authenticationResponse.getResponse();
 
             if (response != null) {
                 return response;
             }
 
-            FederatedIdentity socialUser = authenticationResponse.getUser();
+            FederatedIdentity identity = authenticationResponse.getUser();
 
-            return performLocalAuthentication(realm, providerId, socialUser, clientCode);
+            return performLocalAuthentication(realm, providerId, identity, clientCode);
         } catch (Exception e) {
             if (session.getTransaction().isActive()) {
                 session.getTransaction().rollback();
@@ -192,14 +275,14 @@ public class AuthenticationBrokerResource {
         }
     }
 
-    private Response performLocalAuthentication(RealmModel realm, String providerId, FederatedIdentity socialUser, ClientSessionCode clientCode) {
+    private Response performLocalAuthentication(RealmModel realm, String providerId, FederatedIdentity identity, ClientSessionCode clientCode) {
         ClientSessionModel clientSession = clientCode.getClientSession();
-        FederatedIdentityModel socialLink = new FederatedIdentityModel(providerId, socialUser.getId(),
-                socialUser.getUsername());
-        UserModel federatedUser = session.users().getUserByFederatedIdentity(socialLink, realm);
+        FederatedIdentityModel identityModel = new FederatedIdentityModel(providerId, identity.getId(),
+                identity.getUsername(), identity.getToken());
+        UserModel federatedUser = session.users().getUserByFederatedIdentity(identityModel, realm);
         IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
 
-        String authMethod = socialLink.getUserId() + "@" + identityProviderConfig.getId();
+        String authMethod = identityModel.getUserId() + "@" + identityProviderConfig.getId();
         EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder()
                 .event(EventType.LOGIN)
                 .client(clientSession.getClient())
@@ -228,29 +311,29 @@ public class AuthenticationBrokerResource {
                 return redirectToErrorPage(realm, "Insufficient permissions to link identity");
             }
 
-            session.users().addFederatedIdentity(realm, authenticatedUser, socialLink);
+            session.users().addFederatedIdentity(realm, authenticatedUser, identityModel);
 
             event.success();
 
             return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
         }
 
-        UserModel user = session.users().getUserByEmail(socialUser.getEmail(), realm);
+        UserModel user = session.users().getUserByEmail(identity.getEmail(), realm);
         String errorMessage = "federatedIdentityEmailExists";
 
         if (user == null) {
-            user = session.users().getUserByUsername(socialUser.getUsername(), realm);
+            user = session.users().getUserByUsername(identity.getUsername(), realm);
             errorMessage = "federatedIdentityUsernameExists";
         }
 
         if (user == null) {
-            federatedUser = session.users().addUser(realm, socialUser.getUsername());
+            federatedUser = session.users().addUser(realm, identity.getUsername());
             federatedUser.setEnabled(true);
-            federatedUser.setFirstName(socialUser.getFirstName());
-            federatedUser.setLastName(socialUser.getLastName());
-            federatedUser.setEmail(socialUser.getEmail());
+            federatedUser.setFirstName(identity.getFirstName());
+            federatedUser.setLastName(identity.getLastName());
+            federatedUser.setEmail(identity.getEmail());
 
-            session.users().addFederatedIdentity(realm, federatedUser, socialLink);
+            session.users().addFederatedIdentity(realm, federatedUser, identityModel);
 
             event.clone().user(federatedUser).event(EventType.REGISTER)
                     .detail(Details.REGISTER_METHOD, authMethod)
@@ -272,7 +355,7 @@ public class AuthenticationBrokerResource {
 
         event.user(federatedUser);
 
-        String username = socialLink.getUserId() + "@" + identityProviderConfig.getName();
+        String username = identityModel.getUserId() + "@" + identityProviderConfig.getName();
 
         UserSessionModel userSession = session.sessions()
                 .createUserSession(realm, federatedUser, username, clientConnection.getRemoteAddr(), "broker", false);
@@ -353,4 +436,16 @@ public class AuthenticationBrokerResource {
         return null;
     }
 
+    private Response checkClientPermissions(ClientModel clientModel, String providerId) {
+        if (clientModel == null) {
+            return Flows.errors().error("Invalid client.", Response.Status.FORBIDDEN);
+        }
+
+        if (!clientModel.hasIdentityProvider(providerId)) {
+            return Flows.errors().error("Client [" + clientModel.getClientId() + "] not authorized.", Response.Status.FORBIDDEN);
+        }
+
+        return null;
+    }
+
 }
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
index 8c070be..5266c68 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
@@ -24,8 +24,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider imp
         config.setUserInfoUrl(PROFILE_URL);
     }
 
-    @Override
-    protected FederatedIdentity getFederatedIdentity(String accessToken) {
+    protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
         try {
             JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
index 5069591..7be16dc 100755
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
@@ -25,7 +25,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
     }
 
     @Override
-    protected FederatedIdentity getFederatedIdentity(String accessToken) {
+    protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
         try {
             JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
 
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 0e7673c..1b7a563 100755
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -27,12 +27,14 @@ import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
 import org.keycloak.broker.provider.FederatedIdentity;
 import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.social.SocialIdentityProvider;
 import twitter4j.Twitter;
 import twitter4j.TwitterFactory;
 import twitter4j.auth.RequestToken;
 
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 
@@ -109,4 +111,9 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
             throw new RuntimeException(e);
         }
     }
+
+    @Override
+    public Response retrieveToken(FederatedIdentityModel identity) {
+        return Response.noContent().build();
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index a3329d8..29c6aab 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -25,7 +25,6 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.events.Details;
@@ -220,7 +219,7 @@ public class AccountTest {
 
         changePasswordPage.logout();
 
-        events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AccountPasswordPage.PATH).assertEvent();
+        events.expectLogout(sessionId).detail(Details.REDIRECT_URI, changePasswordPage.getPath()).assertEvent();
 
         loginPage.open();
         loginPage.login("test-user@localhost", "password");
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 da5d014..4d3c4f4 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
@@ -23,28 +23,43 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.IDToken;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testsuite.rule.WebRule.HtmlUnitDriver;
 import org.openqa.selenium.By;
 import org.openqa.selenium.NoSuchElementException;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
 import java.util.List;
 import java.util.Set;
 
 import static com.thoughtworks.selenium.SeleneseTestBase.fail;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -68,6 +83,15 @@ public abstract class AbstractIdentityProviderTest {
     @WebResource
     private LoginUpdateProfilePage updateProfilePage;
 
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected OAuthGrantPage grantPage;
+
+    @WebResource
+    protected AccountPasswordPage changePasswordPage;
+
     private KeycloakSession session;
 
     @Before
@@ -76,6 +100,7 @@ public abstract class AbstractIdentityProviderTest {
         removeTestUsers();
         brokerServerRule.stopSession(this.session, true);
         this.session = brokerServerRule.startSession();
+        assertNotNull(getIdentityProviderModel());
     }
 
     @After
@@ -87,8 +112,6 @@ public abstract class AbstractIdentityProviderTest {
     public void testSuccessfulAuthentication() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
 
-        identityProviderModel.setUpdateProfileFirstLogin(true);
-
         assertSuccessfulAuthentication(identityProviderModel);
     }
 
@@ -96,8 +119,6 @@ public abstract class AbstractIdentityProviderTest {
     public void testSuccessfulAuthenticationWithoutUpdateProfile() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
 
-        identityProviderModel.setUpdateProfileFirstLogin(false);
-
         assertSuccessfulAuthentication(identityProviderModel);
     }
 
@@ -121,10 +142,6 @@ public abstract class AbstractIdentityProviderTest {
 
     @Test
     public void testUserAlreadyExistsWhenUpdatingProfile() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
-        identityProviderModel.setUpdateProfileFirstLogin(true);
-
         this.driver.navigate().to("http://localhost:8081/test-app/");
 
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
@@ -185,10 +202,66 @@ public abstract class AbstractIdentityProviderTest {
         assertEquals("User with email already exists. Please login to account management to link the account.", element.getText());
     }
 
-    private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel) {
+    @Test(expected = NoSuchElementException.class)
+    public void testIdentityProviderNotAllowed() {
+        this.driver.navigate().to("http://localhost:8081/test-app/");
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+
+        driver.findElement(By.className("model-oidc-idp"));
+    }
+
+    @Test
+    public void testTokenStorageAndRetrievalByApplication() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+        identityProviderModel.setStoreToken(true);
+
+        authenticateWithIdentityProvider(identityProviderModel);
+
+        UserModel federatedUser = getFederatedUser();
+        RealmModel realm = getRealm();
+        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+        assertFalse(federatedIdentities.isEmpty());
+        assertEquals(1, federatedIdentities.size());
+
+        FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
+
+        assertNotNull(identityModel.getToken());
+
+        UserSessionStatus userSessionStatus = retrieveSessionStatus();
+        String accessToken = userSessionStatus.getAccessTokenString();
+        String tokenEndpointUrl = "http://localhost:8081/auth/broker/realm-with-broker/" + getProviderId() + "/token";
+        final String authHeader = "Bearer " + accessToken;
+        ClientRequestFilter authFilter = new ClientRequestFilter() {
+            @Override
+            public void filter(ClientRequestContext requestContext) throws IOException {
+                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+            }
+        };
+        Client client = ClientBuilder.newBuilder().register(authFilter).build();
+        UriBuilder authBase = UriBuilder.fromUri(tokenEndpointUrl);
+        WebTarget tokenEndpoint = client.target(authBase);
+        Response response = tokenEndpoint.request().get();
+
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertNotNull(response.readEntity(String.class));
+
+        driver.navigate().to("http://localhost:8081/test-app/logout");
         driver.navigate().to("http://localhost:8081/test-app");
 
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+    }
+
+    @Test
+    public void testTokenStorageAndRetrievalByOAuthClient() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+        identityProviderModel.setStoreToken(true);
+        identityProviderModel.setUpdateProfileFirstLogin(false);
+
+        driver.navigate().to("http://localhost:8081/test-app");
 
         // choose the identity provider
         this.loginPage.clickSocial(getProviderId());
@@ -200,15 +273,42 @@ public abstract class AbstractIdentityProviderTest {
 
         doAfterProviderAuthentication();
 
-        if (identityProviderModel.isUpdateProfileFirstLogin()) {
-            String userEmail = "new@email.com";
-            String userFirstName = "New first";
-            String userLastName = "New last";
+        changePasswordPage.realm("realm-with-broker");
+        changePasswordPage.open();
+        changePasswordPage.changePassword("password", "password");
 
-            // update profile
-            this.updateProfilePage.assertCurrent();
-            this.updateProfilePage.update(userFirstName, userLastName, userEmail);
-        }
+        driver.navigate().to("http://localhost:8081/test-app/logout");
+
+        oauth.realm("realm-with-broker");
+        oauth.redirectUri("http://localhost:8081/third-party");
+        oauth.clientId("third-party");
+        oauth.doLoginGrant("test-user@localhost", "password");
+
+        grantPage.assertCurrent();
+        grantPage.accept();
+
+        assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
+        AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
+        String tokenEndpointUrl = "http://localhost:8081/auth/broker/realm-with-broker/" + getProviderId() + "/token";
+        String authHeader = "Bearer " + accessToken.getAccessToken();
+        HtmlUnitDriver htmlUnitDriver = (WebRule.HtmlUnitDriver) this.driver;
+
+        htmlUnitDriver.getWebClient().addRequestHeader(HttpHeaders.AUTHORIZATION, authHeader);
+
+        htmlUnitDriver.navigate().to(tokenEndpointUrl);
+
+        grantPage.assertCurrent();
+        grantPage.accept();
+
+        assertNotNull(driver.getPageSource());
+
+        doAssertTokenRetrieval(driver.getPageSource());
+    }
+
+    protected abstract void doAssertTokenRetrieval(String pageSource);
+
+    private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel) {
+        authenticateWithIdentityProvider(identityProviderModel);
 
         // authenticated and redirected to app
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
@@ -217,7 +317,7 @@ public abstract class AbstractIdentityProviderTest {
 
         assertNotNull(federatedUser);
 
-        doAssertFederatedUser(federatedUser);
+        doAssertFederatedUser(federatedUser, identityProviderModel);
 
         RealmModel realm = getRealm();
 
@@ -236,6 +336,32 @@ public abstract class AbstractIdentityProviderTest {
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
     }
 
+    private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel) {
+        driver.navigate().to("http://localhost:8081/test-app");
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+
+        // choose the identity provider
+        this.loginPage.clickSocial(getProviderId());
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+
+        // log in to identity provider
+        this.loginPage.login("test-user", "password");
+
+        doAfterProviderAuthentication();
+
+        if (identityProviderModel.isUpdateProfileFirstLogin()) {
+            String userEmail = "new@email.com";
+            String userFirstName = "New first";
+            String userLastName = "New last";
+
+            // update profile
+            this.updateProfilePage.assertCurrent();
+            this.updateProfilePage.update(userFirstName, userLastName, userEmail);
+        }
+    }
+
     protected UserModel getFederatedUser() {
         UserSessionStatus userSessionStatus = retrieveSessionStatus();
         IDToken idToken = userSessionStatus.getIdToken();
@@ -256,6 +382,9 @@ public abstract class AbstractIdentityProviderTest {
 
         assertNotNull(identityProviderModel);
 
+        identityProviderModel.setUpdateProfileFirstLogin(true);
+        identityProviderModel.setEnabled(true);
+
         return identityProviderModel;
     }
 
@@ -263,9 +392,7 @@ public abstract class AbstractIdentityProviderTest {
         return this.session.realms().getRealm("realm-with-broker");
     }
 
-    protected void doAssertFederatedUser(UserModel federatedUser) {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
+    protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
         if (identityProviderModel.isUpdateProfileFirstLogin()) {
             String userEmail = "new@email.com";
             String userFirstName = "New first";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java
index be9503d..deb2611 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java
@@ -35,6 +35,7 @@ public class BrokerKeyCloakRule extends AbstractKeycloakRule {
         server.importRealm(getClass().getResourceAsStream("/broker-test/test-realm-with-broker.json"));
         URL url = getClass().getResource("/broker-test/test-app-keycloak.json");
         deployApplication("test-app", "/test-app", UserSessionStatusServlet.class, url.getPath(), "manager");
+        deployApplication("test-app-allowed-providers", "/test-app-allowed-providers", UserSessionStatusServlet.class, url.getPath(), "manager");
     }
 
     @Override
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 d8e7594..9a646cb 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
@@ -76,6 +76,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         identityProviderModel.getConfig().put("config-added", "value-added");
         identityProviderModel.setEnabled(false);
         identityProviderModel.setUpdateProfileFirstLogin(false);
+        identityProviderModel.setStoreToken(true);
 
         realm.updateIdentityProvider(identityProviderModel);
 
@@ -87,8 +88,9 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         assertEquals("Changed Name", identityProviderModel.getName());
         assertEquals("value-added", identityProviderModel.getConfig().get("config-added"));
-        assertEquals(false, identityProviderModel.isEnabled());
-        assertEquals(false, identityProviderModel.isUpdateProfileFirstLogin());
+        assertFalse(identityProviderModel.isEnabled());
+        assertFalse(identityProviderModel.isUpdateProfileFirstLogin());
+        assertTrue(identityProviderModel.isStoreToken());
 
         identityProviderModel.setName("Changed Name Again");
         identityProviderModel.getConfig().remove("config-added");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 011af1b..66e4b71 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -3,11 +3,18 @@ package org.keycloak.testsuite.broker;
 import org.junit.ClassRule;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.pages.OAuthGrantPage;
 import org.keycloak.testsuite.rule.AbstractKeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testutils.KeycloakServer;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 /**
  * @author pedroigor
@@ -39,6 +46,18 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
     }
 
     @Override
+    protected void doAssertTokenRetrieval(String pageSource) {
+        try {
+            AccessTokenResponse accessTokenResponse = JsonSerialization.readValue(pageSource, AccessTokenResponse.class);
+
+            assertNotNull(accessTokenResponse.getToken());
+            assertNotNull(accessTokenResponse.getIdToken());
+        } catch (IOException e) {
+            fail("Could not parse token.");
+        }
+    }
+
+    @Override
     protected String getProviderId() {
         return "kc-oidc-idp";
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
index 4ef9241..0c0e369 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
@@ -20,8 +20,11 @@ package org.keycloak.testsuite.broker.provider;
 import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 
+import javax.ws.rs.core.Response;
+
 /**
  * @author pedroigor
  */
@@ -45,4 +48,9 @@ public class CustomIdentityProvider extends AbstractIdentityProvider<IdentityPro
     public AuthenticationResponse handleResponse(AuthenticationRequest request) {
         return null;
     }
+
+    @Override
+    public Response retrieveToken(FederatedIdentityModel identity) {
+        return null;
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java
index 86aa6a3..e3a2bd2 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java
@@ -20,9 +20,12 @@ package org.keycloak.testsuite.broker.provider.social;
 import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.social.SocialIdentityProvider;
 
+import javax.ws.rs.core.Response;
+
 /**
  * @author pedroigor
  */
@@ -46,4 +49,9 @@ public class CustomSocialProvider extends AbstractIdentityProvider<IdentityProvi
     public AuthenticationResponse handleResponse(AuthenticationRequest request) {
         return null;
     }
+
+    @Override
+    public Response retrieveToken(FederatedIdentityModel identity) {
+        return null;
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
index b14328a..7783665 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
@@ -8,9 +8,17 @@ import org.keycloak.models.UserModel;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.rule.AbstractKeycloakRule;
 import org.keycloak.testutils.KeycloakServer;
+import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+
+import java.net.URLDecoder;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 /**
  * @author pedroigor
@@ -37,15 +45,27 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
     }
 
     @Override
-    protected void doAssertFederatedUser(UserModel federatedUser) {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
+    protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
         if (identityProviderModel.isUpdateProfileFirstLogin()) {
-            super.doAssertFederatedUser(federatedUser);
+            super.doAssertFederatedUser(federatedUser, identityProviderModel);
         } else {
             assertEquals("test-user@localhost", federatedUser.getEmail());
             assertNull(federatedUser.getFirstName());
             assertNull(federatedUser.getLastName());
         }
     }
+
+    @Override
+    protected void doAssertTokenRetrieval(String pageSource) {
+        try {
+            SAML2Request saml2Request = new SAML2Request();
+            ResponseType responseType = (ResponseType) saml2Request
+                    .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
+
+            assertNotNull(responseType);
+            assertFalse(responseType.getAssertions().isEmpty());
+        } catch (Exception e) {
+            fail("Could not parse token.");
+        }
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
index 47ddb14..e387097 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
@@ -8,9 +8,17 @@ import org.keycloak.models.UserModel;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.rule.AbstractKeycloakRule;
 import org.keycloak.testutils.KeycloakServer;
+import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+
+import java.net.URLDecoder;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 /**
  * @author pedroigor
@@ -37,15 +45,27 @@ public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityP
     }
 
     @Override
-    protected void doAssertFederatedUser(UserModel federatedUser) {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
+    protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
         if (identityProviderModel.isUpdateProfileFirstLogin()) {
-            super.doAssertFederatedUser(federatedUser);
+            super.doAssertFederatedUser(federatedUser, identityProviderModel);
         } else {
             assertEquals("test-user@localhost", federatedUser.getEmail());
             assertNull(federatedUser.getFirstName());
             assertNull(federatedUser.getLastName());
         }
     }
+
+    @Override
+    protected void doAssertTokenRetrieval(String pageSource) {
+        try {
+            SAML2Request saml2Request = new SAML2Request();
+            ResponseType responseType = (ResponseType) saml2Request
+                        .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
+
+            assertNotNull(responseType);
+            assertFalse(responseType.getAssertions().isEmpty());
+        } catch (Exception e) {
+            fail("Could not parse token.");
+        }
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java
index c2a6fdd..b135473 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java
@@ -20,6 +20,7 @@ package org.keycloak.testsuite.broker.util;
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.IDToken;
 
 import javax.servlet.ServletException;
@@ -49,7 +50,8 @@ public class UserSessionStatusServlet extends HttpServlet {
     private void writeSessionStatus(HttpServletRequest req, HttpServletResponse resp) throws IOException {
         KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
         IDToken idToken = context.getIdToken();
-        JsonNode jsonNode = new ObjectMapper().valueToTree(new UserSessionStatus(idToken));
+        AccessToken accessToken = context.getToken();
+        JsonNode jsonNode = new ObjectMapper().valueToTree(new UserSessionStatus(idToken, accessToken, context.getTokenString()));
         PrintWriter writer = resp.getWriter();
 
         writer.println(jsonNode.toString());
@@ -59,14 +61,18 @@ public class UserSessionStatusServlet extends HttpServlet {
 
     public static class UserSessionStatus implements Serializable {
 
+        private String accessTokenString;
+        private AccessToken accessToken;
         private IDToken idToken;
 
         public UserSessionStatus() {
 
         }
 
-        public UserSessionStatus(IDToken idToken) {
+        public UserSessionStatus(IDToken idToken, AccessToken accessToken, String tokenString) {
             this.idToken = idToken;
+            this.accessToken = accessToken;
+            this.accessTokenString = tokenString;
         }
 
         public IDToken getIdToken() {
@@ -76,5 +82,17 @@ public class UserSessionStatusServlet extends HttpServlet {
         public void setIdToken(IDToken idToken) {
             this.idToken = idToken;
         }
+
+        public AccessToken getAccessToken() {
+            return this.accessToken;
+        }
+
+        public void setAccessToken(AccessToken accessToken) {
+            this.accessToken = accessToken;
+        }
+
+        public String getAccessTokenString() {
+            return this.accessTokenString;
+        }
     }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index 0dc027b..975806e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -33,8 +33,6 @@ import javax.ws.rs.core.UriBuilder;
  */
 public class AccountPasswordPage extends AbstractAccountPage {
 
-    public static String PATH = AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
-
     @FindBy(id = "password")
     private WebElement passwordInput;
 
@@ -47,6 +45,8 @@ public class AccountPasswordPage extends AbstractAccountPage {
     @FindBy(className = "btn-primary")
     private WebElement submitButton;
 
+    private String realmName = "test";
+
     public void changePassword(String password, String newPassword, String passwordConfirm) {
         passwordInput.sendKeys(password);
         newPasswordInput.sendKeys(newPassword);
@@ -55,12 +55,26 @@ public class AccountPasswordPage extends AbstractAccountPage {
         submitButton.click();
     }
 
+    public void changePassword(String newPassword, String passwordConfirm) {
+        newPasswordInput.sendKeys(newPassword);
+        passwordConfirmInput.sendKeys(passwordConfirm);
+
+        submitButton.click();
+    }
+
     public boolean isCurrent() {
         return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/password");
     }
 
     public void open() {
-        driver.navigate().to(PATH);
+        driver.navigate().to(getPath());
     }
 
+    public void realm(String realmName) {
+        this.realmName = realmName;
+    }
+
+    public String getPath() {
+        return AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build(this.realmName).toString();
+    }
 }
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 d5865c1..2c98048 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
@@ -3,6 +3,7 @@
     "realm": "realm-with-broker",
     "enabled": true,
     "requiredCredentials": [ "password" ],
+    "resetPasswordAllowed": true,
     "defaultRoles": [ "manager" ],
     "privateKey": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eAguHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3IywJYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK218qQp9R4GZFtf/Q/RmboNXN7weMINU8GWVkTRrccKBIXSunT6zXGfuj3Wp1YpVq20BWwY2OMM/P+yDAc7LKEO1LJqPBdT4r9BRn2lXiaga3AL24gTKZPKU/tu7uqfFciF+i4Rr58SMDNOzQcnklAgMBAAECggEAc0eibJYEO5d8QXW1kPgcHV2gBChv2mxDYnWYDLbIQSdNdfYP/qABt/MTmm5KkWr16fcCEYoD1w0mqFBrtVn1msSusUmEAYGTXJMNumOmjjX1kzaTQMmqeFBrwqwYz/xehWR5P+A7fSmwNV3KEeW19GvN5w5K96w0TLAQdFV3TQVPSytusDunwuR1yltMe1voaEDZ9z0Pi08YiEk2f6xhj5CMkoiw3mNImzfruphHullxU4FD05fH6tDeJ381527ILpAzDsgYZh4aFLKjUHem96bX4EL7FIzBJ6okgN78AZnUC/EaVfgFTw0qfhoWvZV4ruVXXiMhCg4CMMRDq/k9iQKBgQDBNWsJMT84OnnWmQoJmZogkFV+tsGrSK6Re+aJxLWpishh7dwAnT2OcagZvVdUb0FwNWu1D0B9/SKDDMRnnHBhOGDpH57m/eQdRU0oX1BD27xvffk0lLcfD4BTxnR5e9jss8K4twc9jf0P1rxC/loGJ2NtCH0BrPHgz54Ea+96ewKBgQCsk3JDaaPnFwzVYm2BXlhxOxLPsF4wvD2rIRAswZV4C5xebjand8nwiMmVpNd0PRLkEnkI+waURGv2EY/P3JsssoiY8Xqe8f/1G+SQKre7lbqOas8rFoALepC0BYDiZDFy0Z9ZnRAFzRI5sgIt7jpoMRD4xDNlmiV8X+yBxc3Y3wKBgQChDQsU1YUyNKQ8+sLAL9anEEkD4Ald4q8JPHN2IY+gLLxNzT0XEfsu0pTiJ8805axxgUYv3e/PVYNAJBNPnrqaf6lgiegl+jr9Hzhqz9CTUAYqFaL2boSakoxQyNtsLI0s+cb1vDN/3uy0GDZDzcty18BsMagqDmRtFgNNAj/UIwKBgQCahbeFBv0cOPZjxisY8Bou4N8aGehsqNBq/0LVYExuXa8YmoTTdJ3bgw9Er4G/ccQNdUDsuqAMeCtW/CiRzQ0ge4d1sprB4Rv3I4+HSsiS7SFKzfZLtWzXWlpg5qCdlWr1TR7qhYjIOPO9t1beO3YOvwhcRoliyyAPenBxTmTfbwKBgDtm2WJ5VlQgNpIdOs1CCiqd0DFmWOmvBPspPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GFCp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn",
     "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQAB",
@@ -163,7 +164,20 @@
             "redirectUris": [
                 "/test-app/*"
             ],
-            "webOrigins": []
+            "webOrigins": [],
+            "allowedIdentityProviders" : [
+                "model-oidc-idp"
+            ]
+        }
+    ],
+    "oauthClients" : [
+        {
+            "name" : "third-party",
+            "enabled": true,
+            "redirectUris": [
+                "http://localhost:8081/third-party/*"
+            ],
+            "secret": "password"
         }
     ],
     "roles" : {