keycloak-uncached

Changes

Details

diff --git a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
index 328916e..35085c7 100755
--- a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
+++ b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
@@ -342,6 +342,17 @@ public class KeycloakUriBuilder {
     }
 
     /**
+     * Set fragment, but not encode it. It assumes that given fragment was already properly encoded
+     *
+     * @param fragment
+     * @return
+     */
+    public KeycloakUriBuilder encodedFragment(String fragment) {
+        this.fragment = fragment;
+        return this;
+    }
+
+    /**
      * Only replace path params in path of URI.  This changes state of URIBuilder.
      *
      * @param name
diff --git a/core/src/main/java/org/keycloak/representations/RefreshToken.java b/core/src/main/java/org/keycloak/representations/RefreshToken.java
index 39c7c46..0300673 100755
--- a/core/src/main/java/org/keycloak/representations/RefreshToken.java
+++ b/core/src/main/java/org/keycloak/representations/RefreshToken.java
@@ -28,6 +28,7 @@ public class RefreshToken extends AccessToken {
         this.subject = token.subject;
         this.issuedFor = token.issuedFor;
         this.sessionState = token.sessionState;
+        this.nonce = token.nonce;
         if (token.realmAccess != null) {
             realmAccess = token.realmAccess.clone();
         }
diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java
index b9c5338..a995d7b 100755
--- a/events/api/src/main/java/org/keycloak/events/Details.java
+++ b/events/api/src/main/java/org/keycloak/events/Details.java
@@ -11,6 +11,7 @@ public interface Details {
     String CODE_ID = "code_id";
     String REDIRECT_URI = "redirect_uri";
     String RESPONSE_TYPE = "response_type";
+    String RESPONSE_MODE = "response_mode";
     String AUTH_TYPE = "auth_type";
     String AUTH_METHOD = "auth_method";
     String IDENTITY_PROVIDER = "identity_provider";
diff --git a/examples/js-console/src/main/webapp/index.html b/examples/js-console/src/main/webapp/index.html
index 2cca0f8..8c7713f 100644
--- a/examples/js-console/src/main/webapp/index.html
+++ b/examples/js-console/src/main/webapp/index.html
@@ -106,7 +106,13 @@
         event('Auth Logout');
     };
 
-    keycloak.init().success(function(authenticated) {
+    // Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow too in admin console
+    var initOptions = {
+        responseMode: 'fragment',
+        flow: 'standard'
+    };
+
+    keycloak.init(initOptions).success(function(authenticated) {
         output('Init Success (' + (authenticated ? 'Authenticated' : 'Not Authenticated') + ')');
     }).error(function() {
         output('Init Error');
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 21175a0..ffe8328 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -176,7 +176,8 @@ failedLogout=Logout failed
 unknownLoginRequesterMessage=Unknown login requester
 loginRequesterNotEnabledMessage=Login requester not enabled
 bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
-standardFlowDisabledMessage=Client is not allowed to initiate browser login because standard flow is disabled for the client.
+standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
+implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
 invalidRedirectUriMessage=Invalid redirect uri
 unsupportedNameIdFormatMessage=Unsupported NameIDFormat
 invlidRequesterMessage=Invalid requester
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 09e0d86..8f587f0 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -36,8 +36,41 @@
                 if (initOptions.onLoad === 'login-required') {
                     kc.loginRequired = true;
                 }
+
+                if (initOptions.responseMode) {
+                    if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
+                        kc.responseMode = initOptions.responseMode;
+                    } else {
+                        throw 'Invalid value for responseMode';
+                    }
+                }
+
+                if (initOptions.flow) {
+                    switch (initOptions.flow) {
+                        case 'standard':
+                            kc.responseType = 'code';
+                            break;
+                        case 'implicit':
+                            kc.responseType = 'id_token token refresh_token';
+                            break;
+                        case 'hybrid':
+                            kc.responseType = 'code id_token token refresh_token';
+                            break;
+                        default:
+                            throw 'Invalid value for flow';
+                    }
+                }
+            }
+
+            if (!kc.responseMode) {
+                kc.responseMode = 'fragment';
+            }
+            if (!kc.responseType) {
+                kc.responseType = 'code';
             }
 
+            console.log('responseMode=' + kc.responseMode + ', responseType=' + kc.responseType);
+
             var promise = createPromise();
 
             var initPromise = createPromise();
@@ -132,13 +165,14 @@
 
         kc.createLoginUrl = function(options) {
             var state = createUUID();
+            var nonce = createUUID();
 
             var redirectUri = adapter.redirectUri(options);
             if (options && options.prompt) {
                 redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
             }
 
-            sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) });
+            sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
 
             var action = 'auth';
             if (options && options.action == 'register') {
@@ -150,7 +184,9 @@
                 + '?client_id=' + encodeURIComponent(kc.clientId)
                 + '&redirect_uri=' + encodeURIComponent(redirectUri)
                 + '&state=' + encodeURIComponent(state)
-                + '&response_type=code';
+                + '&nonce=' + encodeURIComponent(nonce)
+                + '&response_mode=' + encodeURIComponent(kc.responseMode)
+                + '&response_type=' + encodeURIComponent(kc.responseType);
 
             if (options && options.prompt) {
                 url += '&prompt=' + encodeURIComponent(options.prompt);
@@ -394,6 +430,8 @@
             var error = oauth.error;
             var prompt = oauth.prompt;
 
+            var timeLocal = new Date().getTime();
+
             if (code) {
                 var params = 'code=' + code + '&grant_type=authorization_code';
                 var url = getRealmUrl() + '/protocol/openid-connect/token';
@@ -412,20 +450,12 @@
 
                 req.withCredentials = true;
 
-                var timeLocal = new Date().getTime();
-
                 req.onreadystatechange = function() {
                     if (req.readyState == 4) {
                         if (req.status == 200) {
-                            timeLocal = (timeLocal + new Date().getTime()) / 2;
 
                             var tokenResponse = JSON.parse(req.responseText);
-                            setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
-
-                            kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
-
-                            kc.onAuthSuccess && kc.onAuthSuccess();
-                            promise && promise.setSuccess();
+                            authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'])
                         } else {
                             kc.onAuthError && kc.onAuthError();
                             promise && promise.setError();
@@ -441,7 +471,31 @@
                 } 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) {
+                timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+                setToken(accessToken, refreshToken, idToken);
+
+                if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
+                    (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
+                    (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
+
+                    console.log('invalid nonce!');
+                    kc.clearToken();
+                    promise && promise.setError();
+                } else {
+                    kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+
+                    kc.onAuthSuccess && kc.onAuthSuccess();
+                    promise && promise.setSuccess();
+                }
+            }
+
         }
 
         function loadConfig(url) {
@@ -597,53 +651,21 @@
         }
 
         function parseCallback(url) {
-            if (url.indexOf('?') != -1) {
-                var oauth = {};
+            var oauth = new CallbackParser(url, kc.responseMode).parseUri();
 
-                oauth.newUrl = url.split('?')[0];
-                var paramString = url.split('?')[1];
-                var fragIndex = paramString.indexOf('#');
-                if (fragIndex != -1) {
-                    paramString = paramString.substring(0, fragIndex);
-                }
-                var params = paramString.split('&');
-                for (var i = 0; i < params.length; i++) {
-                    var p = params[i].split('=');
-                    switch (decodeURIComponent(p[0])) {
-                        case 'code':
-                            oauth.code = p[1];
-                            break;
-                        case 'error':
-                            oauth.error = p[1];
-                            break;
-                        case 'state':
-                            oauth.state = decodeURIComponent(p[1]);
-                            break;
-                        case 'redirect_fragment':
-                            oauth.fragment = decodeURIComponent(p[1]);
-                            break;
-                        case 'prompt':
-                            oauth.prompt = p[1];
-                            break;
-                        default:
-                            oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1];
-                            break;
-                    }
-                }
+            var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
 
-                var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
+            if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
+                delete sessionStorage.oauthState;
 
-                if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) {
-                    delete sessionStorage.oauthState;
+                oauth.redirectUri = sessionState.redirectUri;
+                oauth.storedNonce = sessionState.nonce;
 
-                    oauth.redirectUri = sessionState.redirectUri;
-
-                    if (oauth.fragment) {
-                        oauth.newUrl += '#' + oauth.fragment;
-                    }
-
-                    return oauth;
+                if (oauth.fragment) {
+                    oauth.newUrl += '#' + oauth.fragment;
                 }
+
+                return oauth;
             }
         }
 
@@ -907,6 +929,105 @@
 
             throw 'invalid adapter type: ' + type;
         }
+
+
+        var CallbackParser = function(uriToParse, responseMode) {
+            if (!(this instanceof CallbackParser)) {
+                return new CallbackParser(uriToParse, responseMode);
+            }
+            var parser = this;
+
+            var initialParse = function() {
+                var baseUri = null;
+                var queryString = null;
+                var fragmentString = null;
+
+                var questionMarkIndex = uriToParse.indexOf("?");
+                var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1);
+                if (questionMarkIndex == -1 && fragmentIndex == -1) {
+                    baseUri = uriToParse;
+                } else if (questionMarkIndex != -1) {
+                    baseUri = uriToParse.substring(0, questionMarkIndex);
+                    queryString = uriToParse.substring(questionMarkIndex + 1);
+                    if (fragmentIndex != -1) {
+                        fragmentIndex = queryString.indexOf("#");
+                        fragmentString = queryString.substring(fragmentIndex + 1);
+                        queryString = queryString.substring(0, fragmentIndex);
+                    }
+                } else {
+                    baseUri = uriToParse.substring(0, fragmentIndex);
+                    fragmentString = uriToParse.substring(fragmentIndex + 1);
+                }
+
+                return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString };
+            }
+
+            var parseParams = function(paramString) {
+                var result = {};
+                var params = paramString.split('&');
+                for (var i = 0; i < params.length; i++) {
+                    var p = params[i].split('=');
+                    var paramName = decodeURIComponent(p[0]);
+                    var paramValue = decodeURIComponent(p[1]);
+                    result[paramName] = paramValue;
+                }
+                return result;
+            }
+
+            var handleQueryParam = function(paramName, paramValue, oauth) {
+                var supportedOAuthParams = [ 'code', 'error', 'state' ];
+
+                for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
+                    if (paramName === supportedOAuthParams[i]) {
+                        oauth[paramName] = paramValue;
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+
+            parser.parseUri = function() {
+                var parsedUri = initialParse();
+
+                var queryParams = {};
+                if (parsedUri.queryString) {
+                    queryParams = parseParams(parsedUri.queryString);
+                }
+
+                var oauth = { newUrl: parsedUri.baseUri };
+                for (var param in queryParams) {
+                    switch (param) {
+                        case 'redirect_fragment':
+                            oauth.fragment = queryParams[param];
+                            break;
+                        case 'prompt':
+                            oauth.prompt = queryParams[param];
+                            break;
+                        default:
+                            if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
+                                oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
+                            }
+                            break;
+                    }
+                }
+
+                if (responseMode === 'fragment') {
+                    var fragmentParams = {};
+                    if (parsedUri.fragmentString) {
+                        fragmentParams = parseParams(parsedUri.fragmentString);
+                    }
+                    for (var param in fragmentParams) {
+                        oauth[param] = fragmentParams[param];
+                    }
+                }
+
+                console.log("OAUTH: ");
+                console.log(oauth);
+                return oauth;
+            }
+        }
+
     }
 
     if ( typeof module === "object" && module && typeof module.exports === "object" ) {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index b1d4162..710c77d 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -470,7 +470,8 @@ public class AuthenticationProcessor {
             LoginProtocol protocol = getSession().getProvider(LoginProtocol.class, getClientSession().getAuthMethod());
             protocol.setRealm(getRealm())
                     .setHttpHeaders(getHttpRequest().getHttpHeaders())
-                    .setUriInfo(getUriInfo());
+                    .setUriInfo(getUriInfo())
+                    .setEventBuilder(event);
             Response response = protocol.sendError(getClientSession(), Error.CANCELLED_BY_USER);
             forceChallenge(response);
         }
@@ -808,7 +809,7 @@ public class AuthenticationProcessor {
     public Response finishAuthentication() {
         event.success();
         RealmModel realm = clientSession.getRealm();
-        return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection);
+        return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event);
 
     }
 
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 250de12..4b95d56 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
@@ -1,19 +1,11 @@
 package org.keycloak.protocol.oidc.endpoints;
 
-import java.util.List;
-
 import javax.ws.rs.GET;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
 
 import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.common.ClientConnection;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
@@ -24,12 +16,12 @@ import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.AuthorizationEndpointBase;
-import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.services.ErrorPageException;
 import org.keycloak.services.Urls;
@@ -55,11 +47,13 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     private ClientSessionModel clientSession;
 
     private Action action;
+    private OIDCResponseType parsedResponseType;
 
     private String clientId;
     private String redirectUri;
     private String redirectUriParam;
     private String responseType;
+    private String responseMode;
     private String state;
     private String scope;
     private String loginHint;
@@ -80,6 +74,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
 
         clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
         responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
         redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
         state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
         scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
@@ -90,8 +85,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
 
         checkSsl();
         checkRealm();
-        checkClient();
         checkResponseType();
+        checkClient();
         checkRedirectUri();
 
         createClientSession();
@@ -172,11 +167,17 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
             throw new ErrorPageException(session, Messages.BEARER_ONLY);
         }
 
-        if (!client.isStandardFlowEnabled()) {
+        if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
             event.error(Errors.NOT_ALLOWED);
             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()) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ErrorPageException(session, Messages.IMPLICIT_FLOW_DISABLED);
+        }
+
         session.getContext().setClient(client);
     }
 
@@ -192,14 +193,32 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
 
         event.detail(Details.RESPONSE_TYPE, responseType);
 
-        if (responseType.equals(OAuth2Constants.CODE)) {
+        try {
+            parsedResponseType = OIDCResponseType.parse(responseType);
             if (action == null) {
                 action = Action.CODE;
             }
-        } else {
+        } catch (IllegalArgumentException iae) {
+            logger.error(iae.getMessage());
             event.error(Errors.INVALID_REQUEST);
             throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
         }
+
+        try {
+            OIDCResponseMode parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType);
+            event.detail(Details.RESPONSE_MODE, parsedResponseMode.toString().toLowerCase());
+
+            // Disallowed by OIDC specs
+            if (parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY) {
+                logger.error("Response_mode 'query' not allowed for implicit or hybrid flow");
+                event.error(Errors.INVALID_REQUEST);
+                throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+            }
+
+        } catch (IllegalArgumentException iae) {
+            event.error(Errors.INVALID_REQUEST);
+            throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        }
     }
 
     private void checkRedirectUri() {
@@ -228,6 +247,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
         if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
         if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint);
+        if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode);
     }
 
     private Response buildAuthorizationCodeAuthorizationResponse() {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index c790661..fe358e0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -327,7 +327,7 @@ public class TokenEndpoint {
     }
 
     public Response buildResourceOwnerPasswordCredentialsGrant() {
-        event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
+        event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD);
 
         if (client.isConsentRequired()) {
             event.error(Errors.CONSENT_DENIED);
@@ -393,7 +393,7 @@ public class TokenEndpoint {
             throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
         }
 
-        event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
+        event.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
 
         UserModel clientUser = session.users().getUserByServiceAccountClient(client);
 
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 b9d55db..76c9794 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -33,6 +33,10 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.ResourceAdminManager;
 
@@ -62,6 +66,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
     public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
     public static final String ISSUER = "iss";
 
+    public static final String RESPONSE_MODE_PARAM = "response_mode";
+
     private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
 
     protected KeycloakSession session;
@@ -74,6 +80,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     protected EventBuilder event;
 
+    protected OIDCResponseType responseType;
+    protected OIDCResponseMode responseMode;
+
     public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
         this.session = session;
         this.realm = realm;
@@ -86,6 +95,15 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     }
 
+    private void setupResponseTypeAndMode(ClientSessionModel clientSession) {
+        String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        String responseMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        this.responseType = OIDCResponseType.parse(responseType);
+        this.responseMode = OIDCResponseMode.parse(responseMode, this.responseType);
+        this.event.detail(Details.RESPONSE_TYPE, responseType);
+        this.event.detail(Details.RESPONSE_MODE, this.responseMode.toString().toLowerCase());
+    }
+
     @Override
     public OIDCLoginProtocol setSession(KeycloakSession session) {
         this.session = session;
@@ -116,32 +134,70 @@ public class OIDCLoginProtocol implements LoginProtocol {
         return this;
     }
 
+
     @Override
     public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
         ClientSessionModel clientSession = accessCode.getClientSession();
+        setupResponseTypeAndMode(clientSession);
+
         String redirect = clientSession.getRedirectUri();
+        OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
         String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
-        accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
         log.debugv("redirectAccessCode: state: {0}", state);
         if (state != null)
-            redirectUri.queryParam(OAuth2Constants.STATE, state);
-        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+            redirectUri.addParam(OAuth2Constants.STATE, state);
 
-        return location.build();
+        // Standard or hybrid flow
+        if (responseType.hasResponseType(OIDCResponseType.CODE)) {
+            accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
+            redirectUri.addParam(OAuth2Constants.CODE, accessCode.getCode());
+        }
+
+        // Implicit or hybrid flow
+        if (responseType.hasResponseType(OIDCResponseType.TOKEN) || responseType.hasResponseType(OIDCResponseType.ID_TOKEN) || responseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN)) {
+            TokenManager tokenManager = new TokenManager();
+            AccessTokenResponse res = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession)
+                    .generateAccessToken()
+                    .generateRefreshToken()
+                    .generateIDToken()
+                    .build();
+
+            if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
+                redirectUri.addParam("id_token", res.getIdToken());
+            }
+
+            if (responseType.hasResponseType(OIDCResponseType.TOKEN)) {
+                redirectUri.addParam("access_token", res.getToken());
+                redirectUri.addParam("token_type", res.getTokenType());
+                redirectUri.addParam("session-state", res.getSessionState());
+                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()));
+        }
+
+        return redirectUri.build();
     }
 
+
     @Override
     public Response sendError(ClientSessionModel clientSession, Error error) {
+        setupResponseTypeAndMode(clientSession);
+
         String redirect = clientSession.getRedirectUri();
         String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, translateError(error));
+        OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode).addParam(OAuth2Constants.ERROR, translateError(error));
         if (state != null)
-            redirectUri.queryParam(OAuth2Constants.STATE, state);
+            redirectUri.addParam(OAuth2Constants.STATE, state);
         session.sessions().removeClientSession(realm, clientSession);
         RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
-        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
-        return location.build();
+        return redirectUri.build();
     }
 
     private String translateError(Error error) {
@@ -161,10 +217,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     @Override
     public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
-        if (!(clientSession.getClient() instanceof ClientModel))
-            return;
-        ClientModel app = clientSession.getClient();
-        new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
+        ClientModel client = clientSession.getClient();
+        new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession);
     }
 
     @Override
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 304e6af..b2966c0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -52,7 +52,7 @@ import java.util.Map;
 import java.util.Set;
 
 /**
- * Stateful object that creates tokens and manages oauth access codes
+ * Stateless object that creates tokens and manages oauth access codes
  *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
new file mode 100644
index 0000000..06cc3cc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
@@ -0,0 +1,149 @@
+package org.keycloak.protocol.oidc.utils;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.common.util.Encode;
+import org.keycloak.common.util.KeycloakUriBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class OIDCRedirectUriBuilder {
+
+    protected final KeycloakUriBuilder uriBuilder;
+
+    protected OIDCRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+        this.uriBuilder = uriBuilder;
+    }
+
+    public abstract OIDCRedirectUriBuilder addParam(String paramName, String paramValue);
+    public abstract Response build();
+
+
+    public static OIDCRedirectUriBuilder fromUri(String baseUri, OIDCResponseMode responseMode) {
+        KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(baseUri);
+
+        switch (responseMode) {
+            case QUERY: return new QueryRedirectUriBuilder(uriBuilder);
+            case FRAGMENT: return new FragmentRedirectUriBuilder(uriBuilder);
+            case FORM_POST: return new FormPostRedirectUriBuilder(uriBuilder);
+        }
+
+        throw new IllegalStateException("Not possible to end here");
+    }
+
+
+    // Impl subclasses
+
+
+    // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
+    public static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+        protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+            super(uriBuilder);
+        }
+
+        @Override
+        public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+            uriBuilder.queryParam(paramName, paramValue);
+            return this;
+        }
+
+        @Override
+        public Response build() {
+            URI redirectUri = uriBuilder.build();
+            Response.ResponseBuilder location = Response.status(302).location(redirectUri);
+            return location.build();
+        }
+    }
+
+
+    // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
+    public static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+        private StringBuilder fragment;
+
+        protected FragmentRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+            super(uriBuilder);
+        }
+
+        @Override
+        public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+            String param = paramName + "=" + Encode.encodeQueryParam(paramValue);
+            if (fragment == null) {
+                fragment = new StringBuilder(param);
+            } else {
+                fragment.append("&").append(param);
+            }
+            return this;
+        }
+
+        @Override
+        public Response build() {
+            if (fragment != null) {
+                uriBuilder.encodedFragment(fragment.toString());
+            }
+            URI redirectUri = uriBuilder.build();
+
+            Response.ResponseBuilder location = Response.status(302).location(redirectUri);
+            return location.build();
+        }
+
+    }
+
+
+    // http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
+    public static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+        private Map<String, String> params = new HashMap<>();
+
+        protected FormPostRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+            super(uriBuilder);
+        }
+
+        @Override
+        public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+            params.put(paramName, Encode.encodeQueryParam(paramValue));
+            return this;
+        }
+
+        @Override
+        public Response build() {
+            StringBuilder builder = new StringBuilder();
+            URI redirectUri = uriBuilder.build();
+
+            builder.append("<HTML>");
+            builder.append("  <HEAD>");
+            builder.append("    <TITLE>OIDC Form_Post Response</TITLE>");
+            builder.append("  </HEAD>");
+            builder.append("  <BODY Onload=\"document.forms[0].submit()\">");
+
+            builder.append("    <FORM METHOD=\"POST\" ACTION=\"" + redirectUri.toString() + "\">");
+
+            for (Map.Entry<String, String> param : params.entrySet()) {
+                builder.append("  <INPUT TYPE=\"HIDDEN\" NAME=\"").append(param.getKey())
+                        .append("\" VALUE=\"").append(param.getValue()).append("\" />");
+            }
+
+            builder.append("      <NOSCRIPT>");
+            builder.append("        <P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>");
+            builder.append("        <INPUT name=\"continue\" TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
+            builder.append("      </NOSCRIPT>");
+            builder.append("    </FORM>");
+            builder.append("  </BODY>");
+            builder.append("</HTML>");
+
+            return Response.status(Response.Status.OK)
+                    .type(MediaType.TEXT_HTML_TYPE)
+                    .entity(builder.toString()).build();
+        }
+
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java
new file mode 100644
index 0000000..c255ccc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java
@@ -0,0 +1,25 @@
+package org.keycloak.protocol.oidc.utils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public enum OIDCResponseMode {
+
+    QUERY, FRAGMENT, FORM_POST;
+
+    public static OIDCResponseMode parse(String responseMode, OIDCResponseType responseType) {
+        if (responseMode == null) {
+            return getDefaultResponseMode(responseType);
+        } else {
+            return Enum.valueOf(OIDCResponseMode.class, responseMode.toUpperCase());
+        }
+    }
+
+    private static OIDCResponseMode getDefaultResponseMode(OIDCResponseType responseType) {
+        if (responseType.isImplicitOrHybridFlow()) {
+            return OIDCResponseMode.FRAGMENT;
+        } else {
+            return OIDCResponseMode.QUERY;
+        }
+    }
+}
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
new file mode 100644
index 0000000..5c96de0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
@@ -0,0 +1,72 @@
+package org.keycloak.protocol.oidc.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 final List<String> responseTypes;
+
+
+    private OIDCResponseType(List<String> responseTypes) {
+        this.responseTypes = responseTypes;
+    }
+
+
+    public static OIDCResponseType parse(String responseTypeParam) {
+        if (responseTypeParam == null) {
+            throw new IllegalStateException("response_type is null");
+        }
+
+        String[] responseTypes = responseTypeParam.trim().split(" ");
+        List<String> allowedTypes = new ArrayList<>();
+        for (String current : responseTypes) {
+            if (ALLOWED_RESPONSE_TYPES.contains(current)) {
+                allowedTypes.add(current);
+            } else {
+                throw new IllegalStateException("Unsupported response_type: " + responseTypeParam);
+            }
+        }
+        return new OIDCResponseType(allowedTypes);
+    }
+
+
+    public boolean hasResponseType(String responseType) {
+        return responseTypes.contains(responseType);
+    }
+
+
+    public boolean isImplicitOrHybridFlow() {
+        return hasResponseType(TOKEN) || hasResponseType(ID_TOKEN) || hasResponseType(REFRESH_TOKEN);
+    }
+
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        boolean first = true;
+        for (String responseType : responseTypes) {
+            if (!first) {
+                builder.append(" ");
+            } else {
+                first = false;
+            }
+            builder.append(responseType);
+        }
+        return builder.toString();
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index c820e4d..0131b6d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -380,7 +380,8 @@ public class AuthenticationManager {
 
     public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
                                                 ClientSessionModel clientSession,
-                                                HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
+                                                HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
+                                                EventBuilder event) {
         Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
         if (sessionCookie != null) {
 
@@ -407,7 +408,8 @@ public class AuthenticationManager {
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
         protocol.setRealm(realm)
                 .setHttpHeaders(request.getHttpHeaders())
-                .setUriInfo(uriInfo);
+                .setUriInfo(uriInfo)
+                .setEventBuilder(event);
         RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo);
         return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
 
@@ -429,7 +431,7 @@ public class AuthenticationManager {
         }
         event.success();
         RealmModel realm = clientSession.getRealm();
-        return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
+        return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection, event);
 
     }
 
@@ -522,9 +524,11 @@ public class AuthenticationManager {
                 LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
                 protocol.setRealm(context.getRealm())
                         .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
-                        .setUriInfo(context.getUriInfo());
+                        .setUriInfo(context.getUriInfo())
+                        .setEventBuilder(event);
+                Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
                 event.error(Errors.REJECTED_BY_USER);
-                return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+                return response;
             }
             else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
                 clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 6b22606..ca1db96 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -112,6 +112,8 @@ public class Messages {
 
     public static final String STANDARD_FLOW_DISABLED = "standardFlowDisabledMessage";
 
+    public static final String IMPLICIT_FLOW_DISABLED = "implicitFlowDisabledMessage";
+
     public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage";
 
     public static final String UNSUPPORTED_NAME_ID_FORMAT = "unsupportedNameIdFormatMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index bc83df5..36e4c6c 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -59,6 +59,8 @@ import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.LoginProtocol.Error;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
 import org.keycloak.services.ErrorPage;
 import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
@@ -546,7 +548,7 @@ public class LoginActionsService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processConsent(final MultivaluedMap<String, String> formData) {
-        event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
+        event.event(EventType.LOGIN);
 
 
         if (!checkSsl()) {
@@ -561,38 +563,28 @@ public class LoginActionsService {
             return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE);
         }
         ClientSessionModel clientSession = accessCode.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
 
-        String redirect = clientSession.getRedirectUri();
+        initEvent(clientSession);
+
         UserSessionModel userSession = clientSession.getUserSession();
         UserModel user = userSession.getUser();
         ClientModel client = clientSession.getClient();
 
-        event.client(client)
-                .user(user)
-                .detail(Details.RESPONSE_TYPE, "code")
-                .detail(Details.REDIRECT_URI, redirect);
-
-        event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
-        event.detail(Details.USERNAME, userSession.getLoginUsername());
-        if (userSession.isRememberMe()) {
-            event.detail(Details.REMEMBER_ME, "true");
-        }
-
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
             AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
         }
-        event.session(userSession);
 
         if (formData.containsKey("cancel")) {
             LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
             protocol.setRealm(realm)
                     .setHttpHeaders(headers)
-                    .setUriInfo(uriInfo);
+                    .setUriInfo(uriInfo)
+                    .setEventBuilder(event);
+            Response response = protocol.sendError(clientSession, Error.CONSENT_DENIED);
             event.error(Errors.REJECTED_BY_USER);
-            return protocol.sendError(clientSession, Error.CONSENT_DENIED);
+            return response;
         }
 
         UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
@@ -613,7 +605,7 @@ public class LoginActionsService {
         event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
         event.success();
 
-        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
+        return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event);
     }
 
     @Path("email-verification")
@@ -727,22 +719,27 @@ public class LoginActionsService {
     }
 
     private void initEvent(ClientSessionModel clientSession) {
+        UserSessionModel userSession = clientSession.getUserSession();
+
+        String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        if (responseType == null) {
+            responseType = "code";
+        }
+        String respMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));
+
         event.event(EventType.LOGIN).client(clientSession.getClient())
-                .user(clientSession.getUserSession().getUser())
-                .session(clientSession.getUserSession().getId())
+                .user(userSession.getUser())
+                .session(userSession.getId())
                 .detail(Details.CODE_ID, clientSession.getId())
                 .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
                 .detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
-                .detail(Details.RESPONSE_TYPE, "code");
-
-        UserSessionModel userSession = clientSession.getUserSession();
-
-        if (userSession != null) {
-            event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
-            event.detail(Details.USERNAME, userSession.getLoginUsername());
-            if (userSession.isRememberMe()) {
-                event.detail(Details.REMEMBER_ME, "true");
-            }
+                .detail(Details.AUTH_METHOD, userSession.getAuthMethod())
+                .detail(Details.USERNAME, userSession.getLoginUsername())
+                .detail(Details.RESPONSE_TYPE, responseType)
+                .detail(Details.RESPONSE_MODE, responseMode.toString().toLowerCase());
+        if (userSession.isRememberMe()) {
+            event.detail(Details.REMEMBER_ME, "true");
         }
     }
 
@@ -827,9 +824,14 @@ public class LoginActionsService {
             LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
             protocol.setRealm(context.getRealm())
                     .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
-                    .setUriInfo(context.getUriInfo());
-            event.detail(Details.CUSTOM_REQUIRED_ACTION, action).error(Errors.REJECTED_BY_USER);
-            return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+                    .setUriInfo(context.getUriInfo())
+                    .setEventBuilder(event);
+
+            event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
+            Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+            event.error(Errors.REJECTED_BY_USER);
+            return response;
+
         }
 
         throw new RuntimeException("Unreachable");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 74c70ea..84e724b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -8,6 +8,7 @@ import org.junit.Assert;
 import org.junit.rules.TestRule;
 import org.junit.runners.model.Statement;
 import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
 import org.keycloak.common.constants.ServiceAccountConstants;
 import org.keycloak.events.admin.AdminEvent;
@@ -134,7 +135,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
         return expect(EventType.CLIENT_LOGIN)
                 .detail(Details.CODE_ID, isCodeId())
                 .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
-                .detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH)
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
                 .removeDetail(Details.CODE_ID)
                 .session(isUUID());
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
index f704553..4690308 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
@@ -218,7 +218,7 @@ public class CustomFlowTest {
                 .client(clientId)
                 .user(userId)
                 .session(accessToken.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, accessToken.getId())
                 .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
                 .detail(Details.USERNAME, login)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
index 97250c4..a5f2388 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
@@ -7,6 +7,7 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.admin.client.resource.GroupResource;
 import org.keycloak.admin.client.resource.RealmResource;
@@ -256,7 +257,7 @@ public class GroupTest {
                 .client(clientId)
                 .user(userId)
                 .session(accessToken.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, accessToken.getId())
                 .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
                 .detail(Details.USERNAME, login)
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 d3a47e0..5287189 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
@@ -30,6 +30,7 @@ import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
@@ -164,8 +165,8 @@ public class AuthorizationCodeTest {
         UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
         b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token");
         driver.navigate().to(b.build().toURL());
-        assertEquals("Invalid parameter: response_type", errorPage.getError());
-        events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
+        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();
     }
 
     private void assertCode(String expectedCodeId, String actualCode) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
index 537a297..898066a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
@@ -189,7 +189,7 @@ public class ClientAuthSignedJWTTest {
         events.expectLogin()
                 .client("client2")
                 .session(accessToken.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, accessToken.getId())
                 .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
                 .detail(Details.USERNAME, "test-user@localhost")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index 6c205ad..d333f86 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -40,6 +40,7 @@ import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
 import java.io.IOException;
+import java.net.URL;
 
 /**
  * @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
@@ -174,7 +175,10 @@ public class OAuthRedirectUriTest {
         OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
 
         Assert.assertNotNull(response.getCode());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?code="));
+        URL url = new URL(driver.getCurrentUrl());
+        Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
+        Assert.assertTrue(url.getQuery().contains("code="));
+        Assert.assertTrue(url.getQuery().contains("state="));
     }
 
     @Test
@@ -192,7 +196,11 @@ public class OAuthRedirectUriTest {
         OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
 
         Assert.assertNotNull(response.getCode());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
+        URL url = new URL(driver.getCurrentUrl());
+        Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
+        Assert.assertTrue(url.getQuery().contains("key=value"));
+        Assert.assertTrue(url.getQuery().contains("state="));
+        Assert.assertTrue(url.getQuery().contains("code="));
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 884d9f0..a4b2855 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -319,7 +319,7 @@ public class OfflineTokenTest {
                 .client("offline-client")
                 .user(userId)
                 .session(token.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, token.getId())
                 .detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
                 .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
@@ -361,7 +361,7 @@ public class OfflineTokenTest {
                 .client("offline-client")
                 .user(userId)
                 .session(token.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, token.getId())
                 .detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
                 .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index 74e3d38..b64a683 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -6,6 +6,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
@@ -93,7 +94,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
                 .client(clientId)
                 .user(userId)
                 .session(accessToken.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, accessToken.getId())
                 .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
                 .detail(Details.USERNAME, login)
@@ -129,7 +130,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
         events.expectLogin()
                 .client("resource-owner")
                 .session(accessToken.getSessionState())
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.TOKEN_ID, accessToken.getId())
                 .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
                 .removeDetail(Details.CODE_ID)
@@ -285,7 +286,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
         events.expectLogin()
                 .client("resource-owner")
                 .session((String) null)
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .removeDetail(Details.CODE_ID)
                 .removeDetail(Details.REDIRECT_URI)
                 .removeDetail(Details.CONSENT)
@@ -307,7 +308,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
                 .client("resource-owner")
                 .user((String) null)
                 .session((String) null)
-                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
                 .detail(Details.USERNAME, "invalid")
                 .removeDetail(Details.CODE_ID)
                 .removeDetail(Details.REDIRECT_URI)