keycloak-aplcache

change logout behavior

8/8/2014 7:33:37 PM

Changes

Details

diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index 0f57ea3..077eb98 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -12,6 +12,7 @@
                 <!ENTITY JBossAdapter SYSTEM "modules/jboss-adapter.xml">
                 <!ENTITY JavascriptAdapter SYSTEM "modules/javascript-adapter.xml">
                 <!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml">
+                <!ENTITY Logout SYSTEM "modules/logout.xml">
                 <!ENTITY SocialConfig SYSTEM "modules/social-config.xml">
                 <!ENTITY SocialFacebook SYSTEM "modules/social-facebook.xml">
                 <!ENTITY SocialGitHub SYSTEM "modules/social-github.xml">
@@ -80,6 +81,7 @@ This one is short
         &JBossAdapter;
         &JavascriptAdapter;
         &InstalledApplications;
+        &Logout;
     </chapter>
 
     <chapter>
diff --git a/docbook/reference/en/en-US/modules/direct-access.xml b/docbook/reference/en/en-US/modules/direct-access.xml
index 1074537..c08735b 100755
--- a/docbook/reference/en/en-US/modules/direct-access.xml
+++ b/docbook/reference/en/en-US/modules/direct-access.xml
@@ -88,7 +88,7 @@ try {
    if (isPublic()) { // if client is public access type
        formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal"));
    } else {
-       String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret);
+       String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret");
        post.setHeader("Authorization", authorization);
    }
    UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
@@ -125,4 +125,36 @@ GET /my/rest/api
 Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
 </programlisting>
     </para>
+    <para>
+        To logout you must use the refresh token contained in the AccessTokenResponse object.
+    </para>
+<programlisting>
+    <![CDATA[
+List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+if (isPublic()) { // if client is public access type
+    formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal"));
+} else {
+    String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret");
+    post.setHeader("Authorization", authorization);
+}
+formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, tokenResponse.getRefreshToken()));
+HttpResponse response = null;
+URI logoutUri = KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
+                    .path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
+                    .build("demo");
+HttpPost post = new HttpPost(logoutUri);
+UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+post.setEntity(form);
+response = client.execute(post);
+int status = response.getStatusLine().getStatusCode();
+HttpEntity entity = response.getEntity();
+if (status != 204) {
+   error(status, entity);
+}
+if (entity == null) {
+   return;
+}
+InputStream is = entity.getContent();
+if (is != null) is.close();
+]]></programlisting>
 </chapter>
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/logout.xml b/docbook/reference/en/en-US/modules/logout.xml
new file mode 100755
index 0000000..e2812b8
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/logout.xml
@@ -0,0 +1,9 @@
+<section>
+    <title>Logout</title>
+    <para>
+        There are multiple ways you can logout from a web application.  For Java EE servlet containers, you can call
+        HttpServletRequest.logout().
+        For any other browser application, you can point the browser at the url <literal>http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri</literal>.
+        This will log you out if you have an SSO session with your browser.
+    </para>
+</section>
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index aecaf91..7b5f56b 100755
--- a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -1,6 +1,17 @@
 <chapter id="Migration_from_older_versions">
     <title>Migration from older versions</title>
     <sect1>
+        <title>Migrating from 1.0 Beta 4 to RC-1</title>
+        <itemizedlist>
+            <listitem>
+                logout REST API has been refactored.  The GET request on the logout URI does not take a session_state
+                parameter anymore.  You must be logged in in order to log out the session.
+                You can also POST to the lougt REST URI.  This action requires a valid refresh token to perform the logout.
+                The signature is the same as refresh token minus the grant type form parameter.  See documentation for details.
+            </listitem>
+         </itemizedlist>
+    </sect1>
+    <sect1>
         <title>Migrating from 1.0 Beta 1 to Beta 4</title>
         <itemizedlist>
             <listitem>
diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
index 763dfee..4e26fdf 100755
--- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
+++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
@@ -102,17 +102,23 @@ public class AdminClient {
 
 
         try {
-            HttpGet get = new HttpGet(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
+            HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
                     .path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
-                    .queryParam("session_state", res.getSessionState())
                     .build("demo"));
-            HttpResponse response = client.execute(get);
+            List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+            formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, res.getRefreshToken()));
+            formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "admin-client"));
+            HttpResponse response = client.execute(post);
+            boolean status = response.getStatusLine().getStatusCode() != 204;
             HttpEntity entity = response.getEntity();
             if (entity == null) {
                 return;
             }
             InputStream is = entity.getContent();
             if (is != null) is.close();
+            if (status) {
+                throw new RuntimeException("failed to logout");
+            }
         } finally {
             client.getConnectionManager().shutdown();
         }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index 6442499..5f4325a 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -42,6 +42,14 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
         return super.getTokenString();
     }
 
+    public void logout(KeycloakDeployment deployment) {
+        try {
+            ServerRequest.invokeLogout(deployment, refreshToken);
+        } catch (Exception e) {
+            log.error("failed to invoke remote logout", e);
+        }
+    }
+
     public boolean isActive() {
         return this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
     }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index ac08c80..ed8e91f 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -48,19 +48,41 @@ public class ServerRequest {
         }
     }
 
-    public static void invokeLogout(KeycloakDeployment deployment, String sessionId) throws IOException, HttpFailure {
-        URI uri = deployment.getLogoutUrl().clone().queryParam("session_state", sessionId).build();
-        HttpGet logout = new HttpGet(uri);
-        HttpResponse response = deployment.getClient().execute(logout);
+    public static void invokeLogout(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
+        String client_id = deployment.getResourceName();
+        Map<String, String> credentials = deployment.getResourceCredentials();
+        HttpClient client = deployment.getClient();
+        URI uri = deployment.getLogoutUrl().clone().build();
+        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+        for (Map.Entry<String, String> entry : credentials.entrySet()) {
+            formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+        }
+        formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
+        HttpResponse response = null;
+        HttpPost post = new HttpPost(uri);
+        if (!deployment.isPublicClient()) {
+            String clientSecret = credentials.get(CredentialRepresentation.SECRET);
+            if (clientSecret != null) {
+                String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
+                post.setHeader("Authorization", authorization);
+            }
+        } else {
+            formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
+        }
+
+        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+        post.setEntity(form);
+        response = client.execute(post);
         int status = response.getStatusLine().getStatusCode();
         HttpEntity entity = response.getEntity();
-        if (status != 200) {
+        if (status != 204) {
             error(status, entity);
         }
         if (entity == null) {
             return;
         }
-        entity.getContent().close();
+        InputStream is = entity.getContent();
+        if (is != null) is.close();
     }
 
     public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri) throws HttpFailure, IOException {
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index 52945cb..45707b4 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -64,10 +64,8 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
             Session session = request.getSessionInternal(false);
             if (session != null) {
                 session.removeNote(KeycloakSecurityContext.class.getName());
-                try {
-                    ServerRequest.invokeLogout(deploymentContext.getDeployment(), ksc.getToken().getSessionState());
-                } catch (Exception e) {
-                    log.error("failed to invoke remote logout", e);
+                if (ksc instanceof RefreshableKeycloakSecurityContext) {
+                    ((RefreshableKeycloakSecurityContext)ksc).logout(deploymentContext.getDeployment());
                 }
             }
         }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
index 45522fd..1ccce63 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -81,11 +81,8 @@ public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
                 if (account == null) return;
                 session.removeAttribute(KeycloakSecurityContext.class.getName());
                 session.removeAttribute(KeycloakUndertowAccount.class.getName());
-                String sessionId = account.getKeycloakSecurityContext().getToken().getSessionState();
-                try {
-                    ServerRequest.invokeLogout(deploymentContext.getDeployment(), sessionId);
-                } catch (Exception e) {
-                    log.error("failed to invoke remote logout", e);
+                if (account.getKeycloakSecurityContext() != null) {
+                    account.getKeycloakSecurityContext().logout(deploymentContext.getDeployment());
                 }
             }
         };
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java
index 8e55e5a..8182496 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java
@@ -72,11 +72,8 @@ public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanis
                 KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
                 if (account == null) return;
                 session.removeAttribute(KeycloakUndertowAccount.class.getName());
-                String sessionId = account.getKeycloakSecurityContext().getToken().getSessionState();
-                try {
-                    ServerRequest.invokeLogout(deploymentContext.getDeployment(), sessionId);
-                } catch (Exception e) {
-                    log.error("failed to invoke remote logout", e);
+                if (account.getKeycloakSecurityContext() != null) {
+                    account.getKeycloakSecurityContext().logout(deploymentContext.getDeployment());
                 }
             }
         };
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 0971a07..0fc5834 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -64,23 +64,7 @@ public class TokenManager {
     }
 
     public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
-        JWSInput jws = new JWSInput(encodedRefreshToken);
-        RefreshToken refreshToken = null;
-        try {
-            if (!RSAProvider.verify(jws, realm.getPublicKey())) {
-                throw new RuntimeException("Invalid refresh token");
-            }
-            refreshToken = jws.readJsonContent(RefreshToken.class);
-        } catch (IOException e) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
-        }
-        if (refreshToken.isExpired()) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
-        }
-
-        if (refreshToken.getIssuedAt() < realm.getNotBefore()) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
-        }
+        RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
 
         audit.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
 
@@ -122,6 +106,27 @@ public class TokenManager {
         return accessToken;
     }
 
+    public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
+        JWSInput jws = new JWSInput(encodedRefreshToken);
+        RefreshToken refreshToken = null;
+        try {
+            if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+                throw new RuntimeException("Invalid refresh token");
+            }
+            refreshToken = jws.readJsonContent(RefreshToken.class);
+        } catch (IOException e) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
+        }
+        if (refreshToken.isExpired()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
+        }
+
+        if (refreshToken.getIssuedAt() < realm.getNotBefore()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
+        }
+        return refreshToken;
+    }
+
     public AccessToken createClientAccessToken(Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
         AccessToken token = initToken(realm, client, user, session);
         for (RoleModel role : requestedRoles) {
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index cba599c..df5fbc3 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -31,6 +31,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.RefreshToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.ClientConnection;
 import org.keycloak.services.managers.AccessCode;
@@ -1040,39 +1041,29 @@ public class TokenService {
     }
 
     /**
-     * Logout user session.
+     * Logout user session.  User must be logged in via a session cookie.
      *
-     * @param sessionState
      * @param redirectUri
      * @return
      */
     @Path("logout")
     @GET
     @NoCache
-    public Response logout(final @QueryParam("session_state") String sessionState, final @QueryParam("redirect_uri") String redirectUri) {
+    public Response logout(final @QueryParam("redirect_uri") String redirectUri) {
         // todo do we care if anybody can trigger this?
 
         audit.event(EventType.LOGOUT);
         if (redirectUri != null) {
             audit.detail(Details.REDIRECT_URI, redirectUri);
         }
-        if (sessionState != null) {
-            audit.session(sessionState);
-        }
-
         // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
         AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
         if (authResult != null) {
             logout(authResult.getSession());
-        } else if (sessionState != null) {
-            UserSessionModel userSession = session.sessions().getUserSession(realm, sessionState);
-            if (userSession != null) {
-                logout(userSession);
-            } else {
-                audit.error(Errors.USER_SESSION_NOT_FOUND);
-            }
         } else {
             audit.error(Errors.USER_NOT_LOGGED_IN);
+            OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
+            return oauth.forwardToSecurityFailure("Not logged in.");
         }
 
         if (redirectUri != null) {
@@ -1088,6 +1079,61 @@ public class TokenService {
         }
     }
 
+    /**
+     * Logout a session via a non-browser invocation.  Similar signature to refresh token except there is no grant_type.
+     * You must pass in the refresh token and
+     * authenticate the client if it is not public.
+     *
+     * If the client is a confidential client
+     * you must include the client-id (application name or oauth client name) and secret in an Basic Auth Authorization header.
+     *
+     * If the client is a public client, then you must include a "client_id" form parameter with the app's or oauth client's name.
+     *
+     * returns 204 if successful, 400 if not with a json error response.
+     *
+     * @param authorizationHeader
+     * @param form
+     * @return
+     */
+    @Path("logout")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response logoutToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
+                                       final MultivaluedMap<String, String> form) {
+        logger.info("--> logoutToken");
+        if (!checkSsl()) {
+            throw new NotAcceptableException("HTTPS required");
+        }
+
+        audit.event(EventType.LOGOUT);
+
+        ClientModel client = authorizeClient(authorizationHeader, form, audit);
+        String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
+        if (refreshToken == null) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_REQUEST);
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "No refresh token");
+            audit.error(Errors.INVALID_TOKEN);
+            logger.error("OAuth Error: no refresh token");
+            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
+        }
+        try {
+            RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken);
+            UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
+            if (userSessionModel != null) {
+                logout(userSessionModel);
+            }
+        } catch (OAuthErrorException e) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, e.getError());
+            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
+            audit.error(Errors.INVALID_TOKEN);
+            logger.error("OAuth Error", e);
+            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
+        }
+       return Cors.add(request, Response.noContent()).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    }
+
     private void logout(UserSessionModel userSession) {
         authManager.logout(session, realm, userSession, uriInfo, clientConnection);
         audit.user(userSession.getUser()).session(userSession).success();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 9bb0f49..6e746cc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -214,7 +214,8 @@ public class AdapterTest {
 
 
         driver.navigate().to("http://localhost:8081/customer-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        String currentUrl = driver.getCurrentUrl();
+        Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
         driver.navigate().to("http://localhost:8081/product-portal");
         Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
old mode 100644
new mode 100755
index 4ec9193..26c59e7
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
@@ -124,32 +124,15 @@ public class LogoutTest {
 
         String sessionId = events.expectLogin().assertEvent().getSessionId();
 
-        // Login session 2
-        WebDriver driver2 = WebRule.createWebDriver();
-
-        OAuthClient oauth2 = new OAuthClient(driver2);
-        oauth2.doLogin("test-user@localhost", "password");
-
-        String sessionId2 = events.expectLogin().assertEvent().getSessionId();
-        assertNotEquals(sessionId, sessionId2);
-
         // Check session 1 logged-in
         oauth.openLoginForm();
         events.expectLogin().session(sessionId).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
 
-        // Check session 2 logged-in
-        oauth2.openLoginForm();
-        events.expectLogin().session(sessionId2).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
-
-        // Logout session 1 by redirect
+         //  Logout session 1 by redirect
         driver.navigate().to(oauth.getLogoutUrl(AppPage.baseUrl, null));
         events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AppPage.baseUrl).assertEvent();
 
-        // Check session 2 logged-in
-        oauth2.openLoginForm();
-        events.expectLogin().session(sessionId2).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
-
-        // Check session 1 not logged-in
+         // Check session 1 not logged-in
         oauth.openLoginForm();
         assertEquals(oauth.getLoginFormUrl(), driver.getCurrentUrl());
 
@@ -157,19 +140,10 @@ public class LogoutTest {
         oauth.doLogin("test-user@localhost", "password");
         String sessionId3 = events.expectLogin().assertEvent().getSessionId();
         assertNotEquals(sessionId, sessionId3);
-        assertNotEquals(sessionId2, sessionId3);
-
-        // Logout session 2 by session_state
-        oauth2.doLogout(null, sessionId2);
-        events.expectLogout(sessionId2).removeDetail(Details.REDIRECT_URI).assertEvent();
 
         // Check session 3 logged-in
         oauth.openLoginForm();
         events.expectLogin().session(sessionId3).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
-
-        // Check session 2 not logged-in
-        oauth2.openLoginForm();
-        assertEquals(oauth2.getLoginFormUrl(), driver2.getCurrentUrl());
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index f2ce2b6..55df18f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -305,8 +305,13 @@ public class AccessTokenTest {
         {
             builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
             URI logoutUri = TokenService.logoutUrl(builder).build("test");
-            Response response = client.target(logoutUri).queryParam("session_state", tokenResponse.getSessionState()).request().get();
-            Assert.assertEquals(200, response.getStatus());
+            String header = BasicAuthHelper.createHeader("test-app", "password");
+            Form form = new Form();
+            form.param("refresh_token", tokenResponse.getRefreshToken());
+            Response response = client.target(logoutUri).request()
+                    .header(HttpHeaders.AUTHORIZATION, header)
+                    .post(Entity.form(form));
+            Assert.assertEquals(204, response.getStatus());
             response.close();
         }
         {
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 5a1dad3..1ebf327 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
@@ -131,13 +131,9 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
                 .removeDetail(Details.REDIRECT_URI)
                 .assertEvent();
 
-        HttpResponse logoutResponse = oauth.doLogout(null, accessToken.getSessionState());
-        assertEquals(200, logoutResponse.getStatusLine().getStatusCode());
-        events.expectLogout(accessToken.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
-
-        logoutResponse = oauth.doLogout(null, accessToken.getSessionState());
-        assertEquals(200, logoutResponse.getStatusLine().getStatusCode());
-        events.expectLogout(accessToken.getSessionState()).user((String) null).removeDetail(Details.REDIRECT_URI).error(Errors.USER_SESSION_NOT_FOUND).assertEvent();
+        HttpResponse logoutResponse = oauth.doLogout(response.getRefreshToken(), "secret");
+        assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
+        events.expectLogout(accessToken.getSessionState()).client("resource-owner").removeDetail(Details.REDIRECT_URI).assertEvent();
 
         response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret");
         assertEquals(400, response.getStatusCode());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 3a3f6c8..9a14580 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -171,11 +171,31 @@ public class OAuthClient {
         return new AccessTokenResponse(client.execute(post));
     }
 
-    public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException {
+    public HttpResponse doLogout(String refreshToken, String clientSecret) throws IOException {
         HttpClient client = new DefaultHttpClient();
-        HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState));
+        HttpPost post = new HttpPost(getLogoutUrl(null, null));
 
-        return client.execute(get);
+        List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+        if (refreshToken != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
+        }
+        if (clientId != null && clientSecret != null) {
+            String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
+            post.setHeader("Authorization", authorization);
+        }
+        else if (clientId != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
+        }
+
+        UrlEncodedFormEntity formEntity;
+        try {
+            formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        post.setEntity(formEntity);
+
+        return client.execute(post);
     }
 
     public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {