keycloak-aplcache

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index 1418dc4..eadbdcf 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -51,6 +51,11 @@
                 <constraints nullable="false"/>
             </column>
         </addColumn>
+
+        <addColumn tableName="REALM">
+            <column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
+        </addColumn>
+
         <dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
 
         <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 989c91d..bc9ebf8 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -12,6 +12,7 @@ public class RealmRepresentation {
     protected Integer notBefore;
     protected Boolean revokeRefreshToken;
     protected Integer accessTokenLifespan;
+    protected Integer accessTokenLifespanForImplicitFlow;
     protected Integer ssoSessionIdleTimeout;
     protected Integer ssoSessionMaxLifespan;
     protected Integer offlineSessionIdleTimeout;
@@ -187,6 +188,14 @@ public class RealmRepresentation {
         this.accessTokenLifespan = accessTokenLifespan;
     }
 
+    public Integer getAccessTokenLifespanForImplicitFlow() {
+        return accessTokenLifespanForImplicitFlow;
+    }
+
+    public void setAccessTokenLifespanForImplicitFlow(Integer accessTokenLifespanForImplicitFlow) {
+        this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+    }
+
     public Integer getSsoSessionIdleTimeout() {
         return ssoSessionIdleTimeout;
     }
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml b/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml
index 7f8800e..229ee8d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml
@@ -43,6 +43,12 @@
             application not knowing if the user's permissions have changed.  This value is usually in minutes.
         </para>
         <para>
+            The <code class="literal">Access Token Lifespan For Implicit Flow</code> is how long an access token is valid for when using OpenID Connect implicit flow.
+            With implicit flow, there is no refresh token available, so that's why the lifespan is usually bigger than default Access Token Lifespan mentioned above.
+            See <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect specification</ulink> for details about implicit flow and
+            <link linkend="javascript-adapter">Javascript Adapter</link> for some additional details.
+        </para>
+        <para>
             The <literal>Client login timeout</literal> is how long an access code is valid for.  An access code is obtained
             on the 1st leg of the OAuth 2.0 redirection protocol.  This should be a short time limit.  Usually seconds.
         </para>
diff --git a/examples/js-console/src/main/webapp/index.html b/examples/js-console/src/main/webapp/index.html
index 8c7713f..3789a9f 100644
--- a/examples/js-console/src/main/webapp/index.html
+++ b/examples/js-console/src/main/webapp/index.html
@@ -67,8 +67,11 @@
         var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
         o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n';
 
-        o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
-        o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
+        if (keycloak.refreshTokenParsed) {
+            o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
+            o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
+        }
+
         output(o);
     }
 
@@ -106,7 +109,11 @@
         event('Auth Logout');
     };
 
-    // Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow too in admin console
+    keycloak.onTokenExpired = function () {
+        event('Access token expired.');
+    };
+
+    // Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too 
     var initOptions = {
         responseMode: 'fragment',
         flow: 'standard'
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index d1764e8..b92f65b 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -80,6 +80,8 @@ offline-session-idle=Offline Session Idle
 offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
 access-token-lifespan=Access Token Lifespan
 access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
+access-token-lifespan-for-implicit-flow=Access Token Lifespan For Implicit Flow
+access-token-lifespan-for-implicit-flow.tooltip=Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is separate timeout different to 'Access Token Lifespan'.
 client-login-timeout=Client login timeout
 client-login-timeout.tooltip=Max time an client has to finish the access token protocol. This should normally be 1 minute.
 login-timeout=Login timeout
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index b2c7848..a66e205 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -899,6 +899,12 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         $scope.realm.accessTokenLifespan = TimeUnit.convert($scope.realm.accessTokenLifespan, from, to);
     });
 
+    $scope.realm.accessTokenLifespanForImplicitFlowUnit = TimeUnit.autoUnit(realm.accessTokenLifespanForImplicitFlow);
+    $scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit.toUnit(realm.accessTokenLifespanForImplicitFlow, $scope.realm.accessTokenLifespanForImplicitFlowUnit);
+    $scope.$watch('realm.accessTokenLifespanForImplicitFlowUnit', function(to, from) {
+        $scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit.convert($scope.realm.accessTokenLifespanForImplicitFlow, from, to);
+    });
+
     $scope.realm.ssoSessionIdleTimeoutUnit = TimeUnit.autoUnit(realm.ssoSessionIdleTimeout);
     $scope.realm.ssoSessionIdleTimeout = TimeUnit.toUnit(realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit);
     $scope.$watch('realm.ssoSessionIdleTimeoutUnit', function(to, from) {
@@ -947,6 +953,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
     $scope.save = function() {
         var realmCopy = angular.copy($scope.realm);
         delete realmCopy["accessTokenLifespanUnit"];
+        delete realmCopy["accessTokenLifespanForImplicitFlowUnit"];
         delete realmCopy["ssoSessionMaxLifespanUnit"];
         delete realmCopy["offlineSessionIdleTimeoutUnit"];
         delete realmCopy["accessCodeLifespanUnit"];
@@ -955,6 +962,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, 
         delete realmCopy["accessCodeLifespanLoginUnit"];
 
         realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
+        realmCopy.accessTokenLifespanForImplicitFlow = TimeUnit.toSeconds($scope.realm.accessTokenLifespanForImplicitFlow, $scope.realm.accessTokenLifespanForImplicitFlowUnit)
         realmCopy.ssoSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit)
         realmCopy.ssoSessionMaxLifespan = TimeUnit.toSeconds($scope.realm.ssoSessionMaxLifespan, $scope.realm.ssoSessionMaxLifespanUnit)
         realmCopy.offlineSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.offlineSessionIdleTimeout, $scope.realm.offlineSessionIdleTimeoutUnit)
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
index dee13f6..bdfd390 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
@@ -83,6 +83,23 @@
         </div>
 
         <div class="form-group">
+            <label class="col-md-2 control-label" for="accessTokenLifespanForImplicitFlow">{{:: 'access-token-lifespan-for-implicit-flow' | translate}}</label>
+
+            <div class="col-md-6 time-selector">
+                <input class="form-control" type="number" required min="1"
+                       max="31536000" data-ng-model="realm.accessTokenLifespanForImplicitFlow"
+                       id="accessTokenLifespanForImplicitFlow" name="accessTokenLifespanForImplicitFlow"/>
+                <select class="form-control" name="accessTokenLifespanForImplicitFlowUnit" data-ng-model="realm.accessTokenLifespanForImplicitFlowUnit">
+                    <option data-ng-selected="!realm.accessTokenLifespanForImplicitFlowUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
+                    <option value="Minutes">{{:: 'minutes' | translate}}</option>
+                    <option value="Hours">{{:: 'hours' | translate}}</option>
+                    <option value="Days">{{:: 'days' | translate}}</option>
+                </select>
+            </div>
+            <kc-tooltip>{{:: 'access-token-lifespan-for-implicit-flow.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
             <label class="col-md-2 control-label" for="accessCodeLifespan">{{:: 'client-login-timeout' | translate}}</label>
 
             <div class="col-md-6 time-selector">
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 8f587f0..a74e1cc 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -51,14 +51,15 @@
                             kc.responseType = 'code';
                             break;
                         case 'implicit':
-                            kc.responseType = 'id_token token refresh_token';
+                            kc.responseType = 'id_token token';
                             break;
                         case 'hybrid':
-                            kc.responseType = 'code id_token token refresh_token';
+                            kc.responseType = 'code id_token token';
                             break;
                         default:
                             throw 'Invalid value for flow';
                     }
+                    kc.flow = initOptions.flow;
                 }
             }
 
@@ -67,10 +68,9 @@
             }
             if (!kc.responseType) {
                 kc.responseType = 'code';
+                kc.flow = 'standard';
             }
 
-            console.log('responseMode=' + kc.responseMode + ', responseType=' + kc.responseType);
-
             var promise = createPromise();
 
             var initPromise = createPromise();
@@ -128,7 +128,7 @@
                     return;
                 } else if (initOptions) {
                     if (initOptions.token || initOptions.refreshToken) {
-                        setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
+                        setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
 
                         if (loginIframe.enable) {
                             setupCheckLoginIframe().success(function() {
@@ -313,7 +313,7 @@
         }
 
         kc.isTokenExpired = function(minValidity) {
-            if (!kc.tokenParsed || !kc.refreshToken) {
+            if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) {
                 throw 'Not authenticated';
             }
 
@@ -363,7 +363,7 @@
                                     timeLocal = (timeLocal + new Date().getTime()) / 2;
 
                                     var tokenResponse = JSON.parse(req.responseText);
-                                    setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
+                                    setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
 
                                     kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
 
@@ -401,7 +401,7 @@
 
         kc.clearToken = function() {
             if (kc.token) {
-                setToken(null, null, null);
+                setToken(null, null, null, true);
                 kc.onAuthLogout && kc.onAuthLogout();
                 if (kc.loginRequired) {
                     kc.login();
@@ -432,7 +432,19 @@
 
             var timeLocal = new Date().getTime();
 
-            if (code) {
+            if (error) {
+                if (prompt != 'none') {
+                    kc.onAuthError && kc.onAuthError();
+                    promise && promise.setError();
+                } else {
+                    promise && promise.setSuccess();
+                }
+                return;
+            } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) {
+                authSuccess(oauth.access_token, null, oauth.id_token, true);
+            }
+
+            if ((kc.flow != 'implicit') && code) {
                 var params = 'code=' + code + '&grant_type=authorization_code';
                 var url = getRealmUrl() + '/protocol/openid-connect/token';
 
@@ -455,7 +467,7 @@
                         if (req.status == 200) {
 
                             var tokenResponse = JSON.parse(req.responseText);
-                            authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'])
+                            authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard');
                         } else {
                             kc.onAuthError && kc.onAuthError();
                             promise && promise.setError();
@@ -464,22 +476,12 @@
                 };
 
                 req.send(params);
-            } else if (error) {
-                if (prompt != 'none') {
-                    kc.onAuthError && kc.onAuthError();
-                    promise && promise.setError();
-                } else {
-                    promise && promise.setSuccess();
-                }
-            } else if (oauth.access_token || oauth.id_token || oauth.refresh_token) {
-                authSuccess(oauth.access_token, oauth.refresh_token, oauth.id_token);
             }
 
-
-            function authSuccess(accessToken, refreshToken, idToken) {
+            function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
                 timeLocal = (timeLocal + new Date().getTime()) / 2;
 
-                setToken(accessToken, refreshToken, idToken);
+                setToken(accessToken, refreshToken, idToken, true);
 
                 if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
                     (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
@@ -491,8 +493,10 @@
                 } else {
                     kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
 
-                    kc.onAuthSuccess && kc.onAuthSuccess();
-                    promise && promise.setSuccess();
+                    if (fulfillPromise) {
+                        kc.onAuthSuccess && kc.onAuthSuccess();
+                        promise && promise.setSuccess();
+                    }
                 }
             }
 
@@ -561,7 +565,12 @@
             return promise.promise;
         }
 
-        function setToken(token, refreshToken, idToken) {
+        function setToken(token, refreshToken, idToken, useTokenTime) {
+            if (kc.tokenTimeoutHandle) {
+                clearTimeout(kc.tokenTimeoutHandle);
+                kc.tokenTimeoutHandle = null;
+            }
+
             if (token) {
                 kc.token = token;
                 kc.tokenParsed = decodeToken(token);
@@ -574,6 +583,13 @@
                 kc.subject = kc.tokenParsed.sub;
                 kc.realmAccess = kc.tokenParsed.realm_access;
                 kc.resourceAccess = kc.tokenParsed.resource_access;
+
+                if (kc.onTokenExpired) {
+                    var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
+                    var expiresIn = kc.tokenParsed.exp - start;
+                    kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
+                }
+
             } else {
                 delete kc.token;
                 delete kc.tokenParsed;
@@ -1022,8 +1038,6 @@
                     }
                 }
 
-                console.log("OAUTH: ");
-                console.log(oauth);
                 return oauth;
             }
         }
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
index e5c9484..06df073 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -5,6 +5,7 @@ import org.keycloak.migration.migrators.MigrateTo1_3_0;
 import org.keycloak.migration.migrators.MigrateTo1_4_0;
 import org.keycloak.migration.migrators.MigrateTo1_5_0;
 import org.keycloak.migration.migrators.MigrateTo1_6_0;
+import org.keycloak.migration.migrators.MigrateTo1_7_0;
 import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
 import org.keycloak.models.KeycloakSession;
 
@@ -54,6 +55,12 @@ public class MigrationModelManager {
             }
             new MigrateTo1_6_0().migrate(session);
         }
+        if (stored == null || stored.lessThan(MigrateTo1_7_0.VERSION)) {
+            if (stored != null) {
+                logger.debug("Migrating older model to 1.7.0 updates");
+            }
+            new MigrateTo1_7_0().migrate(session);
+        }
 
         model.setStoredVersion(MigrationModel.LATEST_VERSION);
     }
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
new file mode 100644
index 0000000..e4ead4d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
@@ -0,0 +1,23 @@
+package org.keycloak.migration.migrators;
+
+import java.util.List;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MigrateTo1_7_0 {
+
+    public static final ModelVersion VERSION = new ModelVersion("1.7.0");
+
+    public void migrate(KeycloakSession session) {
+        List<RealmModel> realms = session.realms().getRealms();
+        for (RealmModel realm : realms) {
+            realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
+        }
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 3ecc51c..c50e4cd 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -20,6 +20,8 @@ public interface Constants {
     String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
     String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
 
+    // 15 minutes
+    int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
     // 30 days
     int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
 
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 8f5c844..7a08bca 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -44,6 +44,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private int ssoSessionMaxLifespan;
     private int offlineSessionIdleTimeout;
     private int accessTokenLifespan;
+    private int accessTokenLifespanForImplicitFlow;
     private int accessCodeLifespan;
     private int accessCodeLifespanUserAction;
     private int accessCodeLifespanLogin;
@@ -272,6 +273,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
         this.accessTokenLifespan = accessTokenLifespan;
     }
 
+    public int getAccessTokenLifespanForImplicitFlow() {
+        return accessTokenLifespanForImplicitFlow;
+    }
+
+    public void setAccessTokenLifespanForImplicitFlow(int accessTokenLifespanForImplicitFlow) {
+        this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+    }
+
     public int getAccessCodeLifespan() {
         return accessCodeLifespan;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 62f9162..6c2c260 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -107,6 +107,9 @@ public interface RealmModel extends RoleContainerModel {
 
     void setAccessTokenLifespan(int seconds);
 
+    int getAccessTokenLifespanForImplicitFlow();
+    void setAccessTokenLifespanForImplicitFlow(int seconds);
+
     int getAccessCodeLifespan();
 
     void setAccessCodeLifespan(int seconds);
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 fd88b26..e60d915 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
@@ -205,6 +205,7 @@ public class ModelToRepresentation {
         rep.setEditUsernameAllowed(realm.isEditUsernameAllowed());
         rep.setRevokeRefreshToken(realm.isRevokeRefreshToken());
         rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
+        rep.setAccessTokenLifespanForImplicitFlow(realm.getAccessTokenLifespanForImplicitFlow());
         rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
         rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
         rep.setOfflineSessionIdleTimeout(realm.getOfflineSessionIdleTimeout());
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 82fd2c7..8360e48 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
@@ -105,6 +105,9 @@ public class RepresentationToModel {
         if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
         else newRealm.setAccessTokenLifespan(300);
 
+        if (rep.getAccessTokenLifespanForImplicitFlow() != null) newRealm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
+        else newRealm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
+
         if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
         else newRealm.setSsoSessionIdleTimeout(1800);
         if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
@@ -603,6 +606,7 @@ public class RepresentationToModel {
         if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
         if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
         if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
+        if (rep.getAccessTokenLifespanForImplicitFlow() != null) realm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
         if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
         if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
         if (rep.getOfflineSessionIdleTimeout() != null) realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 02abe5e..c24f151 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -301,6 +301,18 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public int getAccessTokenLifespanForImplicitFlow() {
+        if (updated != null) return updated.getAccessTokenLifespanForImplicitFlow();
+        return cached.getAccessTokenLifespanForImplicitFlow();
+    }
+
+    @Override
+    public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+        getDelegateForUpdate();
+        updated.setAccessTokenLifespanForImplicitFlow(seconds);
+    }
+
+    @Override
     public int getAccessCodeLifespan() {
         if (updated != null) return updated.getAccessCodeLifespan();
         return cached.getAccessCodeLifespan();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 4ae07fd..e423562 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -61,6 +61,7 @@ public class CachedRealm implements Serializable {
     private int ssoSessionMaxLifespan;
     private int offlineSessionIdleTimeout;
     private int accessTokenLifespan;
+    private int accessTokenLifespanForImplicitFlow;
     private int accessCodeLifespan;
     private int accessCodeLifespanUserAction;
     private int accessCodeLifespanLogin;
@@ -146,6 +147,7 @@ public class CachedRealm implements Serializable {
         ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan();
         offlineSessionIdleTimeout = model.getOfflineSessionIdleTimeout();
         accessTokenLifespan = model.getAccessTokenLifespan();
+        accessTokenLifespanForImplicitFlow = model.getAccessTokenLifespanForImplicitFlow();
         accessCodeLifespan = model.getAccessCodeLifespan();
         accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
         accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
@@ -347,6 +349,10 @@ public class CachedRealm implements Serializable {
         return accessTokenLifespan;
     }
 
+    public int getAccessTokenLifespanForImplicitFlow() {
+        return accessTokenLifespanForImplicitFlow;
+    }
+
     public int getAccessCodeLifespan() {
         return accessCodeLifespan;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 6492b81..05af313 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -86,6 +86,8 @@ public class RealmEntity {
     private int offlineSessionIdleTimeout;
     @Column(name="ACCESS_TOKEN_LIFESPAN")
     protected int accessTokenLifespan;
+    @Column(name="ACCESS_TOKEN_LIFE_IMPLICIT")
+    protected int accessTokenLifespanForImplicitFlow;
     @Column(name="ACCESS_CODE_LIFESPAN")
     protected int accessCodeLifespan;
     @Column(name="USER_ACTION_LIFESPAN")
@@ -336,6 +338,14 @@ public class RealmEntity {
         this.accessTokenLifespan = accessTokenLifespan;
     }
 
+    public int getAccessTokenLifespanForImplicitFlow() {
+        return accessTokenLifespanForImplicitFlow;
+    }
+
+    public void setAccessTokenLifespanForImplicitFlow(int accessTokenLifespanForImplicitFlow) {
+        this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+    }
+
     public int getAccessCodeLifespan() {
         return accessCodeLifespan;
     }
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 8c1bb87..cb69f14 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
@@ -361,6 +361,16 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public int getAccessTokenLifespanForImplicitFlow() {
+        return realm.getAccessTokenLifespanForImplicitFlow();
+    }
+
+    @Override
+    public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+        realm.setAccessTokenLifespanForImplicitFlow(seconds);
+    }
+
+    @Override
     public int getSsoSessionIdleTimeout() {
         return realm.getSsoSessionIdleTimeout();
     }
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 9f42cb5..c8fc1f0 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
@@ -369,6 +369,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public int getAccessTokenLifespanForImplicitFlow() {
+        return realm.getAccessTokenLifespanForImplicitFlow();
+    }
+
+    @Override
+    public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+        realm.setAccessTokenLifespanForImplicitFlow(seconds);
+        updateRealm();
+    }
+
+    @Override
     public int getAccessCodeLifespan() {
         return realm.getAccessCodeLifespan();
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 4b95d56..9ed45de 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -172,8 +172,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
             throw new ErrorPageException(session, Messages.STANDARD_FLOW_DISABLED);
         }
 
-        if ((parsedResponseType.hasResponseType(OIDCResponseType.TOKEN) || parsedResponseType.hasResponseType(OIDCResponseType.ID_TOKEN) || parsedResponseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN))
-            && !client.isImplicitFlowEnabled()) {
+        if (parsedResponseType.isImplicitOrHybridFlow() && !client.isImplicitFlowEnabled()) {
             event.error(Errors.NOT_ALLOWED);
             throw new ErrorPageException(session, Messages.IMPLICIT_FLOW_DISABLED);
         }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 76c9794..40bcc67 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -154,11 +154,10 @@ public class OIDCLoginProtocol implements LoginProtocol {
         }
 
         // Implicit or hybrid flow
-        if (responseType.hasResponseType(OIDCResponseType.TOKEN) || responseType.hasResponseType(OIDCResponseType.ID_TOKEN) || responseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN)) {
+        if (responseType.isImplicitOrHybridFlow()) {
             TokenManager tokenManager = new TokenManager();
             AccessTokenResponse res = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession)
                     .generateAccessToken()
-                    .generateRefreshToken()
                     .generateIDToken()
                     .build();
 
@@ -173,12 +172,6 @@ public class OIDCLoginProtocol implements LoginProtocol {
                 redirectUri.addParam("expires_in", String.valueOf(res.getExpiresIn()));
             }
 
-            // Not OIDC standard, but supported
-            if (responseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN)) {
-                redirectUri.addParam("refresh_token", res.getRefreshToken());
-                redirectUri.addParam("refresh_expires_in", String.valueOf(res.getRefreshExpiresIn()));
-            }
-
             redirectUri.addParam("not-before-policy", String.valueOf(res.getNotBeforePolicy()));
         }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index b2966c0..e76734e 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -26,6 +26,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.ProtocolMapper;
 import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
 import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
 import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
@@ -455,8 +456,9 @@ public class TokenManager {
         if (session != null) {
             token.setSessionState(session.getId());
         }
-        if (realm.getAccessTokenLifespan() > 0) {
-            token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
+        int tokenLifespan = getTokenLifespan(realm, clientSession);
+        if (tokenLifespan > 0) {
+            token.expiration(Time.currentTime() + tokenLifespan);
         }
         Set<String> allowedOrigins = client.getWebOrigins();
         if (allowedOrigins != null) {
@@ -465,6 +467,15 @@ public class TokenManager {
         return token;
     }
 
+    private int getTokenLifespan(RealmModel realm, ClientSessionModel clientSession) {
+        boolean implicitFlow = false;
+        String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        if (responseType != null) {
+            implicitFlow = OIDCResponseType.parse(responseType).isImplicitFlow();
+        }
+        return implicitFlow ? realm.getAccessTokenLifespanForImplicitFlow() : realm.getAccessTokenLifespan();
+    }
+
     protected void addComposites(AccessToken token, RoleModel role) {
         AccessToken.Access access = null;
         if (role.getContainer() instanceof RealmModel) {
@@ -582,9 +593,7 @@ public class TokenManager {
             idToken.issuer(accessToken.getIssuer());
             idToken.setNonce(accessToken.getNonce());
             idToken.setSessionState(accessToken.getSessionState());
-            if (realm.getAccessTokenLifespan() > 0) {
-                idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
-            }
+            idToken.expiration(accessToken.getExpiration());
             transformIDToken(session, idToken, realm, client, userSession.getUser(), userSession, clientSession);
             return this;
         }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
index 5c96de0..6377b22 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
@@ -14,10 +14,9 @@ public class OIDCResponseType {
     public static final String CODE = OIDCLoginProtocol.CODE_PARAM;
     public static final String TOKEN = "token";
     public static final String ID_TOKEN = "id_token";
-    public static final String REFRESH_TOKEN = "refresh_token"; // Not officially supported by OIDC
     public static final String NONE = "none";
 
-    private static final List<String> ALLOWED_RESPONSE_TYPES = Arrays.asList(CODE, TOKEN, ID_TOKEN, REFRESH_TOKEN, NONE);
+    private static final List<String> ALLOWED_RESPONSE_TYPES = Arrays.asList(CODE, TOKEN, ID_TOKEN, NONE);
 
     private final List<String> responseTypes;
 
@@ -29,7 +28,7 @@ public class OIDCResponseType {
 
     public static OIDCResponseType parse(String responseTypeParam) {
         if (responseTypeParam == null) {
-            throw new IllegalStateException("response_type is null");
+            throw new IllegalArgumentException("response_type is null");
         }
 
         String[] responseTypes = responseTypeParam.trim().split(" ");
@@ -38,12 +37,30 @@ public class OIDCResponseType {
             if (ALLOWED_RESPONSE_TYPES.contains(current)) {
                 allowedTypes.add(current);
             } else {
-                throw new IllegalStateException("Unsupported response_type: " + responseTypeParam);
+                throw new IllegalArgumentException("Unsupported response_type: " + responseTypeParam);
             }
         }
+
+        validateAllowedTypes(allowedTypes);
+
         return new OIDCResponseType(allowedTypes);
     }
 
+    private static void validateAllowedTypes(List<String> responseTypes) {
+        if (responseTypes.size() == 0) {
+            throw new IllegalStateException("No responseType provided");
+        }
+        if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
+            throw new IllegalArgumentException("None not allowed with some other response_type");
+        }
+        if (responseTypes.contains(ID_TOKEN) && responseTypes.size() == 1) {
+            throw new IllegalArgumentException("Not supported to use response_type=id_token alone");
+        }
+        if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) {
+            throw new IllegalArgumentException("Not supported to use response_type=token alone");
+        }
+    }
+
 
     public boolean hasResponseType(String responseType) {
         return responseTypes.contains(responseType);
@@ -51,7 +68,11 @@ public class OIDCResponseType {
 
 
     public boolean isImplicitOrHybridFlow() {
-        return hasResponseType(TOKEN) || hasResponseType(ID_TOKEN) || hasResponseType(REFRESH_TOKEN);
+        return hasResponseType(TOKEN) || hasResponseType(ID_TOKEN);
+    }
+
+    public boolean isImplicitFlow() {
+        return hasResponseType(TOKEN) && hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 0fa1b92..a868aa8 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -43,6 +43,7 @@ public class ApplianceBootstrap {
             realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
             realm.setSsoSessionIdleTimeout(1800);
             realm.setAccessTokenLifespan(60);
+            realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
             realm.setSsoSessionMaxLifespan(36000);
             realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
             realm.setAccessCodeLifespan(60);
diff --git a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java
new file mode 100644
index 0000000..b3f77a7
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java
@@ -0,0 +1,41 @@
+package org.keycloak.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ResponseTypeTest {
+
+    @Test
+    public void testResponseTypes() {
+        assertFail(null);
+        assertFail("");
+        assertFail("foo");
+        assertSuccess("code");
+        assertSuccess("none");
+        assertFail("id_token");
+        assertFail("token");
+        assertFail("refresh_token");
+        assertSuccess("id_token token");
+        assertSuccess("code token");
+        assertSuccess("code id_token");
+        assertSuccess("code id_token token");
+        assertFail("code none");
+        assertFail("code refresh_token");
+    }
+
+    private void assertSuccess(String responseType) {
+        OIDCResponseType.parse(responseType);
+    }
+
+    private void assertFail(String responseType) {
+        try {
+            OIDCResponseType.parse(responseType);
+            Assert.fail("Not expected to parse '" + responseType + "' with success");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index 9e51065..8147cb3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -255,6 +255,7 @@ public class AdminAPITest {
             Assert.assertEquals(rep.getAccessCodeLifespanUserAction(), storedRealm.getAccessCodeLifespanUserAction());
         if (rep.getNotBefore() != null) Assert.assertEquals(rep.getNotBefore(), storedRealm.getNotBefore());
         if (rep.getAccessTokenLifespan() != null) Assert.assertEquals(rep.getAccessTokenLifespan(), storedRealm.getAccessTokenLifespan());
+        if (rep.getAccessTokenLifespanForImplicitFlow() != null) Assert.assertEquals(rep.getAccessTokenLifespanForImplicitFlow(), storedRealm.getAccessTokenLifespanForImplicitFlow());
         if (rep.getSsoSessionIdleTimeout() != null) Assert.assertEquals(rep.getSsoSessionIdleTimeout(), storedRealm.getSsoSessionIdleTimeout());
         if (rep.getSsoSessionMaxLifespan() != null) Assert.assertEquals(rep.getSsoSessionMaxLifespan(), storedRealm.getSsoSessionMaxLifespan());
         if (rep.getRequiredCredentials() != null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index c11286a..99e5b95 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -74,6 +74,7 @@ public class ImportTest extends AbstractModelTest {
     public static void assertDataImportedInRealm(KeycloakSession session, RealmModel realm) {
         Assert.assertTrue(realm.isVerifyEmail());
         Assert.assertEquals(3600000, realm.getOfflineSessionIdleTimeout());
+        Assert.assertEquals(1500, realm.getAccessTokenLifespanForImplicitFlow());
 
         List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
         Assert.assertEquals(1, creds.size());
@@ -351,6 +352,7 @@ public class ImportTest extends AbstractModelTest {
         RealmModel realm =manager.importRealm(rep);
 
         Assert.assertEquals(600, realm.getAccessCodeLifespanUserAction());
+        Assert.assertEquals(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT, realm.getAccessTokenLifespanForImplicitFlow());
         Assert.assertEquals(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT, realm.getOfflineSessionIdleTimeout());
         verifyRequiredCredentials(realm.getRequiredCredentials(), "password");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 5287189..5d5e0ed 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -161,12 +161,21 @@ public class AuthorizationCodeTest {
     }
 
     @Test
+    public void authorizationRequestImplicitFlowDisabled() throws IOException {
+        UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
+        b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token id_token");
+        driver.navigate().to(b.build().toURL());
+        assertEquals("Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.", errorPage.getError());
+        events.expectLogin().error(Errors.NOT_ALLOWED).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token id_token").assertEvent();
+    }
+
+    @Test
     public void authorizationRequestInvalidResponseType() throws IOException {
         UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
         b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token");
         driver.navigate().to(b.build().toURL());
-        assertEquals("Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.", errorPage.getError());
-        events.expectLogin().error(Errors.NOT_ALLOWED).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, OIDCResponseType.TOKEN).assertEvent();
+        assertEquals("Invalid parameter: response_type", errorPage.getError());
+        events.expectLogin().error(Errors.INVALID_REQUEST).client((String) null).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
     }
 
     private void assertCode(String expectedCodeId, String actualCode) {
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 2935ff6..af86592 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -2,6 +2,7 @@
     "realm": "test-realm",
     "enabled": true,
     "accessTokenLifespan": 6000,
+    "accessTokenLifespanForImplicitFlow": 1500,
     "accessCodeLifespan": 30,
     "accessCodeLifespanUserAction": 600,
     "offlineSessionIdleTimeout": 3600000,
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
index 87eadb5..2be0435 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
@@ -32,8 +32,14 @@ public class CreateClientForm extends Form {
     @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
     private OnOffSwitch consentRequiredSwitch;
 
-    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='directGrantsOnly']]")
-    private OnOffSwitch directGrantsOnlySwitch;
+    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='standardFlowEnabled']]")
+    private OnOffSwitch standardFlowEnabledSwitch;
+
+    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='implicitFlowEnabled']]")
+    private OnOffSwitch implicitFlowEnabledSwitch;
+
+    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='directAccessGrantsEnabled']]")
+    private OnOffSwitch directAccessGrantsEnabledSwitch;
 
     @FindBy(id = "protocol")
     private Select protocolSelect;
@@ -69,7 +75,9 @@ public class CreateClientForm extends Form {
         setName(client.getName());
         setEnabled(client.isEnabled());
         setConsentRequired(client.isConsentRequired());
-        setDirectGrantsOnly(client.isDirectGrantsOnly());
+        setStandardFlowEnabled(client.isStandardFlowEnabled());
+        setImplicitFlowEnabled(client.isImplicitFlowEnabled());
+        setDirectAccessGrantsEnabled(client.isDirectAccessGrantsEnabled());
         setProtocol(client.getProtocol());
         if (OIDC.equals(client.getProtocol())) {
             setAccessType(client);
@@ -88,7 +96,9 @@ public class CreateClientForm extends Form {
         values.setName(getName());
         values.setEnabled(isEnabled());
         values.setConsentRequired(isConsentRequired());
-        values.setDirectGrantsOnly(isDirectGrantsOnly());
+        values.setStandardFlowEnabled(isStandardFlowEnabled());
+        values.setImplicitFlowEnabled(isImplicitFlowEnabled());
+        values.setDirectAccessGrantsEnabled(isDirectAccessGrantsEnabled());
         values.setProtocol(getProtocol());
         if (OIDC.equals(values.getProtocol())) {
             values.setBearerOnly(isBearerOnly());
@@ -195,12 +205,28 @@ public class CreateClientForm extends Form {
         consentRequiredSwitch.setOn(consentRequired);
     }
 
-    public boolean isDirectGrantsOnly() {
-        return directGrantsOnlySwitch.isOn();
+    public boolean isStandardFlowEnabled() {
+        return standardFlowEnabledSwitch.isOn();
+    }
+
+    public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+        standardFlowEnabledSwitch.setOn(standardFlowEnabled);
+    }
+
+    public boolean isImplicitFlowEnabled() {
+        return implicitFlowEnabledSwitch.isOn();
+    }
+
+    public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+        implicitFlowEnabledSwitch.setOn(implicitFlowEnabled);
+    }
+
+    public boolean isDirectAccessGrantsEnabled() {
+        return directAccessGrantsEnabledSwitch.isOn();
     }
 
-    public void setDirectGrantsOnly(boolean directGrantsOnly) {
-        directGrantsOnlySwitch.setOn(directGrantsOnly);
+    public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+        directAccessGrantsEnabledSwitch.setOn(directAccessGrantsEnabled);
     }
 
     public String getProtocol() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
index 6c40d64..94e9b4b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
@@ -56,7 +56,9 @@ public abstract class AbstractClientTest extends AbstractConsoleTest {
         client.setClientId(clientId);
         client.setEnabled(true);
         client.setConsentRequired(false);
-        client.setDirectGrantsOnly(false);
+        client.setStandardFlowEnabled(true);
+        client.setImplicitFlowEnabled(false);
+        client.setDirectAccessGrantsEnabled(true);
         
         client.setProtocol(OIDC);
         
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
index 5229527..d259adf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
@@ -111,7 +111,9 @@ public class ClientSettingsTest extends AbstractClientTest {
         assertEqualsStringAttributes(c1.getName(), c2.getName());
         assertEqualsBooleanAttributes(c1.isEnabled(), c2.isEnabled());
         assertEqualsBooleanAttributes(c1.isConsentRequired(), c2.isConsentRequired());
-        assertEqualsBooleanAttributes(c1.isDirectGrantsOnly(), c2.isDirectGrantsOnly());
+        assertEqualsBooleanAttributes(c1.isStandardFlowEnabled(), c2.isStandardFlowEnabled());
+        assertEqualsBooleanAttributes(c1.isImplicitFlowEnabled(), c2.isImplicitFlowEnabled());
+        assertEqualsBooleanAttributes(c1.isDirectAccessGrantsEnabled(), c2.isDirectAccessGrantsEnabled());
         assertEqualsStringAttributes(c1.getProtocol(), c2.getProtocol());
 
         assertEqualsBooleanAttributes(c1.isBearerOnly(), c2.isBearerOnly());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
index b43bfad..5174acf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
@@ -55,7 +55,7 @@ public class LoginEventsTest extends AbstractConsoleTest {
 
         List<WebElement> resultList = loginEventsPage.table().rows();
 
-        assertEquals(7, resultList.size());
+        assertEquals(8, resultList.size());
         resultList.get(0).findElement(By.xpath("//td[text()='LOGIN']"));
         resultList.get(0).findElement(By.xpath("//td[text()='User']/../td[text()='" + testUser.getId() + "']"));
         resultList.get(0).findElement(By.xpath("//td[text()='Client']/../td[text()='security-admin-console']"));