keycloak-developers

Merge pull request #971 from mposolda/master Add authenticateByDefault

2/13/2015 7:25:46 AM

Changes

Details

diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java
index 56b30fd..84ceb22 100644
--- a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java
@@ -103,15 +103,23 @@ public class KerberosIdentityProvider extends AbstractIdentityProvider<KerberosI
             logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader);
         }
 
-        // Error page is rendered just if browser is unable to send Authorization header with SPNEGO token
-        Response response = request.getSession().getProvider(LoginFormsProvider.class)
+        Response response;
+        LoginFormsProvider loginFormsProvider = request.getSession().getProvider(LoginFormsProvider.class)
                 .setRealm(request.getRealm())
                 .setUriInfo(request.getUriInfo())
-                .setClient(request.getClientSession().getClient())
-                .setClientSessionCode(getRelayState(request))
-                .setWarning("errorKerberosLogin")
-                .setStatus(Response.Status.UNAUTHORIZED)
-                .createLogin();
+                .setStatus(Response.Status.UNAUTHORIZED);
+
+        if (request.getClientSession().getUserSession() == null) {
+            // User not logged. Display HTML with login form as fallback if SPNEGO token not found
+            response = loginFormsProvider.setClient(request.getClientSession().getClient())
+                    .setClientSessionCode(getRelayState(request))
+                    .setWarning("errorKerberosLogin")
+                    .createLogin();
+        } else {
+            // User logged and linking account. Display HTML with error if SPNEGO token not found
+            response = loginFormsProvider.setError("errorKerberosLinkAccount")
+                    .createErrorPage();
+        }
 
         response.getMetadata().putSingle(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader);
         return AuthenticationResponse.fromResponse(response);
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 69a7678..1b553af 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
@@ -23,6 +23,7 @@
             <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="AUTHENTICATE_BY_DEFAULT" type="BOOLEAN(1)"/>
             <column name="REALM_ID" type="VARCHAR(36)"/>
         </createTable>
         <createTable tableName="IDENTITY_PROVIDER_CONFIG">
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_2_0_Beta1.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_2_0_Beta1.java
index a24253c..83d8cc6 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_2_0_Beta1.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_2_0_Beta1.java
@@ -8,6 +8,7 @@ import com.mongodb.BasicDBObjectBuilder;
 import com.mongodb.DBCollection;
 import com.mongodb.DBCursor;
 import com.mongodb.DBObject;
+import org.keycloak.models.utils.KeycloakModelUtils;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -52,11 +53,14 @@ public class Update1_2_0_Beta1 extends Update {
                                 .add("clientSecret", clientSecret).get();
 
                         DBObject identityProvider = new BasicDBObjectBuilder()
+                                .add("internalId", KeycloakModelUtils.generateId())
                                 .add("providerId", socialProviderId)
                                 .add("name", socialProviderId)
                                 .add("id", socialProviderId)
                                 .add("updateProfileFirstLogin", updateProfileOnInitialSocialLogin)
                                 .add("enabled", true)
+                                .add("storeToken", false)
+                                .add("authenticateByDefault", false)
                                 .add("config", identityProviderConfig).get();
 
                         identityProviders.add(identityProvider);
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 b9f09c3..c0b0852 100644
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
@@ -32,6 +32,7 @@ public class IdentityProviderRepresentation {
     protected boolean enabled = true;
     protected boolean updateProfileFirstLogin = true;
     protected boolean storeToken;
+    protected boolean authenticateByDefault;
     protected String groupName;
     protected Map<String, String> config = new HashMap<String, String>();
 
@@ -91,6 +92,14 @@ public class IdentityProviderRepresentation {
         this.updateProfileFirstLogin = updateProfileFirstLogin;
     }
 
+    public boolean isAuthenticateByDefault() {
+        return authenticateByDefault;
+    }
+
+    public void setAuthenticateByDefault(boolean authenticateByDefault) {
+        this.authenticateByDefault = authenticateByDefault;
+    }
+
     public boolean isStoreToken() {
         return this.storeToken;
     }
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index 8766e5c..5c25e1b 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -653,6 +653,8 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
         $scope.identityProvider.name = providerFactory.name;
         $scope.identityProvider.enabled = true;
         $scope.identityProvider.updateProfileFirstLogin = true;
+        // Kerberos is suggested as default provider, others not
+        $scope.identityProvider.authenticateByDefault = (providerFactory.id === "kerberos");
         $scope.newIdentityProvider = true;
     }
 
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html
index 489628a..b061234 100644
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html
@@ -60,6 +60,13 @@
                     </div>
                     <span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
                 </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="authenticateByDefault">Authenticate By Default</label>
+                    <div class="col-sm-4">
+                        <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch />
+                    </div>
+                    <span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
+                </div>
             </fieldset>
 
             <div class="pull-right form-actions">
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 1e4de59..8680ac1 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
@@ -113,6 +113,13 @@
                         </div>
                         <span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
                     </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="authenticateByDefault">Authenticate By Default</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
+                    </div>
                 </fieldset>
 
                 <div class="pull-right form-actions">
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 91cf006..2f08e48 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
@@ -114,6 +114,13 @@
                         </div>
                         <span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
                     </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="authenticateByDefault">Authenticate By Default</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
+                    </div>
                 </fieldset>
 
                 <div class="pull-right form-actions">
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 e5a28e1..3e33cc6 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
@@ -70,6 +70,13 @@
                         </div>
                         <span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
                     </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="authenticateByDefault">Authenticate By Default</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
+                    </div>
                 </fieldset>
 
                 <div class="pull-right form-actions">
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
index 3819d67..c7e1fff 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -34,7 +34,7 @@ invalidPassword=Invalid username or password.
 invalidEmail=Invalid email address
 accountDisabled=Account is disabled, contact admin
 accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
-expiredCode=Login timeout or unknown action. Please login again
+expiredCode=Login timeout. Please login again
 
 missingFirstName=Please specify first name
 missingLastName=Please specify last name
@@ -98,7 +98,8 @@ actionPasswordWarning=You need to change your password to activate your account.
 actionEmailWarning=You need to verify your email address to activate your account.
 actionFollow=Please fill in the fields below.
 
-errorKerberosLogin=Unable to login with Kerberos. Request Kerberos ticket or use different login mechanism
+errorKerberosLogin=Kerberos ticket not available. Use different login mechanism
+errorKerberosLinkAccount=Kerberos ticket not available.
 
 successHeader=Success!
 errorHeader=Error!
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 4fb33f2..3343b90 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
@@ -32,6 +32,7 @@ public class IdentityProviderEntity {
     private boolean enabled;
     private boolean updateProfileFirstLogin;
     private boolean storeToken;
+    private boolean authenticateByDefault;
 
     private Map<String, String> config = new HashMap<String, String>();
 
@@ -67,6 +68,14 @@ public class IdentityProviderEntity {
         this.updateProfileFirstLogin = updateProfileFirstLogin;
     }
 
+    public boolean isAuthenticateByDefault() {
+        return authenticateByDefault;
+    }
+
+    public void setAuthenticateByDefault(boolean authenticateByDefault) {
+        this.authenticateByDefault = authenticateByDefault;
+    }
+
     public boolean isStoreToken() {
         return this.storeToken;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
index 76ca2d8..ca7063f 100644
--- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -53,6 +53,11 @@ public class IdentityProviderModel {
     private boolean storeToken;
 
     /**
+     * Specifies if particular provider should be used by default for authentication even before displaying login screen
+     */
+    private boolean authenticateByDefault;
+
+    /**
      * <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>
      */
@@ -70,6 +75,7 @@ public class IdentityProviderModel {
         this.enabled = model.isEnabled();
         this.updateProfileFirstLogin = model.isUpdateProfileFirstLogin();
         this.storeToken = model.isStoreToken();
+        this.authenticateByDefault = model.isAuthenticateByDefault();
     }
 
     public String getInternalId() {
@@ -128,6 +134,14 @@ public class IdentityProviderModel {
         this.storeToken = storeToken;
     }
 
+    public boolean isAuthenticateByDefault() {
+        return authenticateByDefault;
+    }
+
+    public void setAuthenticateByDefault(boolean authenticateByDefault) {
+        this.authenticateByDefault = authenticateByDefault;
+    }
+
     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 f89acaa..05ccba9 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
@@ -308,6 +308,7 @@ public class ModelToRepresentation {
         providerRep.setEnabled(identityProviderModel.isEnabled());
         providerRep.setStoreToken(identityProviderModel.isStoreToken());
         providerRep.setUpdateProfileFirstLogin(identityProviderModel.isUpdateProfileFirstLogin());
+        providerRep.setAuthenticateByDefault(identityProviderModel.isAuthenticateByDefault());
         providerRep.setConfig(identityProviderModel.getConfig());
 
         return providerRep;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 8463168..8a0fc26 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
@@ -759,6 +759,7 @@ public class RepresentationToModel {
         identityProviderModel.setName(representation.getName());
         identityProviderModel.setEnabled(representation.isEnabled());
         identityProviderModel.setUpdateProfileFirstLogin(representation.isUpdateProfileFirstLogin());
+        identityProviderModel.setAuthenticateByDefault(representation.isAuthenticateByDefault());
         identityProviderModel.setStoreToken(representation.isStoreToken());
         identityProviderModel.setConfig(representation.getConfig());
 
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 408d706..b1a9f10 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
@@ -50,6 +50,9 @@ public class IdentityProviderEntity {
     @Column(name="STORE_TOKEN")
     private boolean storeToken;
 
+    @Column(name="AUTHENTICATE_BY_DEFAULT")
+    private boolean authenticateByDefault;
+
     @ElementCollection
     @MapKeyColumn(name="name")
     @Column(name="value", columnDefinition = "TEXT")
@@ -120,6 +123,14 @@ public class IdentityProviderEntity {
         this.storeToken = storeToken;
     }
 
+    public boolean isAuthenticateByDefault() {
+        return authenticateByDefault;
+    }
+
+    public void setAuthenticateByDefault(boolean authenticateByDefault) {
+        this.authenticateByDefault = authenticateByDefault;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index ce9d1f9..0133829 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.setAuthenticateByDefault(entity.isAuthenticateByDefault());
             identityProviderModel.setStoreToken(entity.isStoreToken());
 
             identityProviders.add(identityProviderModel);
@@ -1150,6 +1151,7 @@ public class RealmAdapter implements RealmModel {
         entity.setEnabled(identityProvider.isEnabled());
         entity.setStoreToken(identityProvider.isStoreToken());
         entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+        entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
         entity.setConfig(identityProvider.getConfig());
 
         realm.addIdentityProvider(entity);
@@ -1176,6 +1178,7 @@ public class RealmAdapter implements RealmModel {
                 entity.setName(identityProvider.getName());
                 entity.setEnabled(identityProvider.isEnabled());
                 entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+                entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
                 entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
             }
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 aa3dcd4..52d4c98 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
@@ -251,7 +251,8 @@ public class MongoUserProvider implements UserProvider {
     @Override
     public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
         FederatedIdentityEntity federatedIdentityEntity = findSocialLink(user, socialProvider, realm);
-        return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName()) : null;
+        return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(),
+                federatedIdentityEntity.getUserName(), federatedIdentityEntity.getToken()) : null;
     }
 
     @Override
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 44058be..a574e1c 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.setAuthenticateByDefault(entity.isAuthenticateByDefault());
             identityProviderModel.setStoreToken(entity.isStoreToken());
 
             identityProviders.add(identityProviderModel);
@@ -825,6 +826,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         entity.setName(identityProvider.getName());
         entity.setEnabled(identityProvider.isEnabled());
         entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+        entity.setStoreToken(identityProvider.isStoreToken());
+        entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
         entity.setConfig(identityProvider.getConfig());
 
         realm.getIdentityProviders().add(entity);
@@ -851,6 +854,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
                 entity.setName(identityProvider.getName());
                 entity.setEnabled(identityProvider.isEnabled());
                 entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+                entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
                 entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
             }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index 2c242d1..fd20b16 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -877,9 +877,7 @@ public class OpenIDConnectService {
                         .setError("Could not find an identity provider with the identifier [" + idpHint + "].")
                         .createErrorPage();
             }
-
-            return Response.temporaryRedirect(
-                    Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), idpHint, this.realm.getName(), accessCode)).build();
+            return redirectToIdentityProvider(idpHint, accessCode);
         }
 
         response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
@@ -890,16 +888,18 @@ public class OpenIDConnectService {
             return oauth.cancelLogin(clientSession);
         }
 
-        List<RequiredCredentialModel> requiredCredentials = realm.getRequiredCredentials();
+        List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+        for (IdentityProviderModel identityProvider : identityProviders) {
+            if (identityProvider.isAuthenticateByDefault()) {
+                return redirectToIdentityProvider(identityProvider.getId(), accessCode);
+            }
+        }
 
+        List<RequiredCredentialModel> requiredCredentials = realm.getRequiredCredentials();
         if (requiredCredentials.isEmpty()) {
-            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
-
             if (!identityProviders.isEmpty()) {
                 if (identityProviders.size() == 1) {
-                    return Response.temporaryRedirect(
-                            Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), identityProviders.get(0).getId(), this.realm.getName(), accessCode))
-                            .build();
+                    return redirectToIdentityProvider(identityProviders.get(0).getId(), accessCode);
                 }
 
                 return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + this.realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage();
@@ -1197,6 +1197,13 @@ public class OpenIDConnectService {
         return Response.status(status).entity(e).type("application/json").build();
     }
 
+    private Response redirectToIdentityProvider(String providerId, String accessCode) {
+        logger.debug("Automatically redirect to identity provider: " + providerId);
+        return Response.temporaryRedirect(
+                Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
+                .build();
+    }
+
     TokenManager getTokenManager() {
         return this.tokenManager;
     }
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 8ac0ee7..9b9d723 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
@@ -80,6 +80,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         identityProviderModel.setEnabled(false);
         identityProviderModel.setUpdateProfileFirstLogin(false);
         identityProviderModel.setStoreToken(true);
+        identityProviderModel.setAuthenticateByDefault(true);
 
         realm.updateIdentityProvider(identityProviderModel);
 
@@ -94,11 +95,13 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertFalse(identityProviderModel.isEnabled());
         assertFalse(identityProviderModel.isUpdateProfileFirstLogin());
         assertTrue(identityProviderModel.isStoreToken());
+        assertTrue(identityProviderModel.isAuthenticateByDefault());
 
         identityProviderModel.setName("Changed Name Again");
         identityProviderModel.getConfig().remove("config-added");
         identityProviderModel.setEnabled(true);
         identityProviderModel.setUpdateProfileFirstLogin(true);
+        identityProviderModel.setAuthenticateByDefault(false);
 
         realm.updateIdentityProvider(identityProviderModel);
 
@@ -109,8 +112,9 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         assertEquals("Changed Name Again", identityProviderModel.getName());
         assertFalse(identityProviderModel.getConfig().containsKey("config-added"));
-        assertEquals(true, identityProviderModel.isEnabled());
-        assertEquals(true, identityProviderModel.isUpdateProfileFirstLogin());
+        assertTrue(identityProviderModel.isEnabled());
+        assertTrue(identityProviderModel.isUpdateProfileFirstLogin());
+        assertFalse(identityProviderModel.isAuthenticateByDefault());
     }
 
     @Test
@@ -175,6 +179,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("Google", config.getName());
         assertEquals(true, config.isEnabled());
         assertEquals(true, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(true, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
         assertEquals(GoogleIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
@@ -192,6 +198,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("SAML Signed IdP", config.getName());
         assertEquals(true, config.isEnabled());
         assertEquals(true, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(false, config.isStoreToken());
         assertEquals("http://localhost:8082/auth/realms/realm-with-saml-identity-provider/protocol/saml", config.getSingleSignOnServiceUrl());
         assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", config.getNameIDPolicyFormat());
         assertEquals("MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin", config.getSigningCertificate());
@@ -211,6 +219,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("OIDC IdP", config.getName());
         assertEquals(false, config.isEnabled());
         assertEquals(false, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
     }
@@ -224,6 +234,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("Facebook", config.getName());
         assertEquals(true, config.isEnabled());
         assertEquals(true, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
         assertEquals(FacebookIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
@@ -240,6 +252,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("GitHub", config.getName());
         assertEquals(true, config.isEnabled());
         assertEquals(true, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
         assertEquals(GitHubIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
@@ -256,6 +270,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("Twitter", config.getName());
         assertEquals(true, config.isEnabled());
         assertEquals(true, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(true, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
     }
@@ -269,6 +285,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("Kerberos", config.getName());
         assertEquals(true, config.isEnabled());
         assertEquals(true, config.isUpdateProfileFirstLogin());
+        assertEquals(false, config.isAuthenticateByDefault());
         assertEquals("HTTP/server.domain.org@DOMAIN.ORG", config.getServerPrincipal());
         assertEquals("/etc/http.keytab", config.getKeyTab());
         assertTrue(config.getDebug());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index eb71e08..a1ef54c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -286,7 +286,7 @@ public class LoginTest {
             loginPage.login("login@test.com", "password");
 
             loginPage.assertCurrent();
-            Assert.assertEquals("Login timeout or unknown action. Please login again", loginPage.getError());
+            Assert.assertEquals("Login timeout. Please login again", loginPage.getError());
 
             events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().assertEvent();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index d308c3e..580ba2a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -164,7 +164,7 @@ public class LoginTotpTest {
             loginTotpPage.login(totp.generate("totpSecret"));
 
             loginPage.assertCurrent();
-            Assert.assertEquals("Login timeout or unknown action. Please login again", loginPage.getError());
+            Assert.assertEquals("Login timeout. Please login again", loginPage.getError());
 
             AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("expired_code")
                     .user((String)null)
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 182b131..bc62fa8 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
@@ -15,6 +15,7 @@
             "name" : "Google",
             "enabled": true,
             "updateProfileFirstLogin" : "true",
+            "storeToken": "true",
             "config": {
                 "clientId": "clientId",
                 "clientSecret": "clientSecret"
@@ -40,6 +41,7 @@
             "name" : "GitHub",
             "enabled": true,
             "updateProfileFirstLogin" : "true",
+            "storeToken": "false",
             "config": {
                 "authorizationUrl": "authorizationUrl",
                 "tokenUrl": "tokenUrl",
@@ -54,6 +56,7 @@
             "name" : "Twitter",
             "enabled": true,
             "updateProfileFirstLogin" : "true",
+            "storeToken": true,
             "config": {
                 "authorizationUrl": "authorizationUrl",
                 "tokenUrl": "tokenUrl",
@@ -116,6 +119,7 @@
             "name" : "OIDC IdP",
             "enabled": false,
             "updateProfileFirstLogin" : "false",
+            "authenticateByDefault" : "false",
             "config": {
                 "clientId": "clientId",
                 "clientSecret": "clientSecret",
@@ -148,6 +152,7 @@
             "name" : "Kerberos",
             "enabled": true,
             "updateProfileFirstLogin" : "true",
+            "authenticateByDefault" : "false",
             "config": {
                 "serverPrincipal": "HTTP/server.domain.org@DOMAIN.ORG",
                 "keyTab": "/etc/http.keytab",