keycloak-aplcache

Merge pull request #1192 from patriot1burke/master broker

4/28/2015 8:18:45 PM

Changes

Details

diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index a231346..f4ddeae 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -64,6 +64,11 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> 
     }
 
     @Override
+    public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
+
+    }
+
+    @Override
     public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
 
     }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index 01d5455..2c503bb 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -76,6 +76,8 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
      */
     Response retrieveToken(FederatedIdentityModel identity);
 
+    void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
+
     /**
      * Called when a Keycloak application initiates a logout through the browser.  This is expected to do a logout
      * with the IDP
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 95e1f6a..b80949a 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -22,7 +22,7 @@ import org.codehaus.jackson.map.ObjectMapper;
 import org.jboss.logging.Logger;
 import org.keycloak.ClientConnection;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
index 3c59a96..d791765 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
@@ -1,6 +1,6 @@
 package org.keycloak.broker.oidc;
 
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.events.EventBuilder;
@@ -74,7 +74,7 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
                             && userSession.getState() != UserSessionModel.State.LOGGING_OUT
                             && userSession.getState() != UserSessionModel.State.LOGGED_OUT
                             ) {
-                        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+                        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
                     }
                 }
 
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index b840f37..01e6c41 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -19,7 +19,8 @@ package org.keycloak.broker.oidc;
 
 import org.codehaus.jackson.JsonNode;
 import org.jboss.logging.Logger;
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
@@ -127,20 +128,51 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
     }
 
     @Override
-    public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
-        if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
+    public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
+        if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("") || !getConfig().isBackchannelSupported()) return;
+        String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
+        if (idToken == null) return;
+        backchannelLogout(userSession, idToken);
+    }
+
+    protected void backchannelLogout(UserSessionModel userSession, String idToken) {
         String sessionId = userSession.getId();
         UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
-                                         .queryParam("state", sessionId);
+                .queryParam("state", sessionId);
+        logoutUri.queryParam("id_token_hint", idToken);
+        String url = logoutUri.build().toString();
+        try {
+            int status = JsonSimpleHttp.doGet(url).asStatus();
+            boolean success = status >=200 && status < 400;
+            if (!success) {
+                logger.warn("Failed backchannel broker logout to: " + url);
+            }
+        } catch (Exception e) {
+            logger.warn("Failed backchannel broker logout to: " + url, e);
+        }
+    }
+
+
+    @Override
+    public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
+        if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
         String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
-        if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
-        String redirect = RealmsResource.brokerUrl(uriInfo)
-                                        .path(IdentityBrokerService.class, "getEndpoint")
-                                        .path(OIDCEndpoint.class, "logoutResponse")
-                                        .build(realm.getName(), getConfig().getAlias()).toString();
-        logoutUri.queryParam("post_logout_redirect_uri", redirect);
-        Response response = Response.status(302).location(logoutUri.build()).build();
-        return response;
+        if (idToken != null && getConfig().isBackchannelSupported()) {
+            backchannelLogout(userSession, idToken);
+            return null;
+        } else {
+            String sessionId = userSession.getId();
+            UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
+                    .queryParam("state", sessionId);
+            if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
+            String redirect = RealmsResource.brokerUrl(uriInfo)
+                    .path(IdentityBrokerService.class, "getEndpoint")
+                    .path(OIDCEndpoint.class, "logoutResponse")
+                    .build(realm.getName(), getConfig().getAlias()).toString();
+            logoutUri.queryParam("post_logout_redirect_uri", redirect);
+            Response response = Response.status(302).location(logoutUri.build()).build();
+            return response;
+        }
     }
 
     @Override
@@ -184,9 +216,9 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
             String email = (String)idToken.getOtherClaims().get(IDToken.EMAIL);
 
             if (getConfig().getUserInfoUrl() != null && (id == null || name == null || preferredUsername == null || email == null) ) {
-                JsonNode userInfo = SimpleHttp.doGet(getConfig().getUserInfoUrl())
-                        .header("Authorization", "Bearer " + accessToken)
-                        .asJson();
+                SimpleHttp request = JsonSimpleHttp.doGet(getConfig().getUserInfoUrl())
+                        .header("Authorization", "Bearer " + accessToken);
+                JsonNode userInfo = JsonSimpleHttp.asJson(request);
 
                 id = getJsonProperty(userInfo, "sub");
                 name = getJsonProperty(userInfo, "name");
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
index ce89bfa..cc40cdb 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
@@ -70,6 +70,14 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
         getConfig().put("validateSignature", String.valueOf(validateSignature));
     }
 
+    public boolean isBackchannelSupported() {
+        return Boolean.valueOf(getConfig().get("backchannelSupported"));
+    }
+
+    public void setBackchannelSupported(boolean backchannel) {
+        getConfig().put("backchannelSupported", String.valueOf(backchannel));
+    }
+
 
 
 
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
index 6c57274..ca0c1bb 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
@@ -17,11 +17,10 @@
  */
 package org.keycloak.broker.oidc;
 
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
 import org.keycloak.jose.jwk.JWK;
 import org.keycloak.jose.jwk.JWKParser;
-import org.keycloak.jose.jws.Algorithm;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/JsonSimpleHttp.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/JsonSimpleHttp.java
new file mode 100755
index 0000000..6f63584
--- /dev/null
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/JsonSimpleHttp.java
@@ -0,0 +1,32 @@
+package org.keycloak.broker.oidc.util;
+
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.keycloak.broker.provider.util.SimpleHttp;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JsonSimpleHttp extends SimpleHttp {
+    public JsonSimpleHttp(String url, String method) {
+        super(url, method);
+    }
+
+    public static JsonSimpleHttp doGet(String url) {
+        return new JsonSimpleHttp(url, "GET");
+    }
+
+    public static JsonSimpleHttp doPost(String url) {
+        return new JsonSimpleHttp(url, "POST");
+    }
+
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    public static JsonNode asJson(SimpleHttp request) throws IOException {
+        return mapper.readTree(request.asString());
+    }
+
+}
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index b3efc97..d2d32eb 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -218,7 +218,7 @@ public class SAMLEndpoint {
                 List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
                 for (UserSessionModel userSession : userSessions) {
                     try {
-                        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+                        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
                     } catch (Exception e) {
                         logger.warn("failed to do backchannel logout for userSession", e);
                     }
@@ -230,7 +230,7 @@ public class SAMLEndpoint {
                     UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
                     if (userSession != null) {
                         try {
-                            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+                            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
                         } catch (Exception e) {
                             logger.warn("failed to do backchannel logout for userSession", e);
                         }
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 98fd6ef..4517a94 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -17,10 +17,12 @@
  */
 package org.keycloak.broker.saml;
 
+import org.jboss.logging.Logger;
 import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.dom.saml.v2.assertion.AssertionType;
 import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
 import org.keycloak.dom.saml.v2.assertion.NameIDType;
@@ -34,12 +36,17 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
 import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
 import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
 
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -48,6 +55,7 @@ import java.security.PublicKey;
  * @author Pedro Igor
  */
 public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityProviderConfig> {
+    protected static final Logger logger = Logger.getLogger(SAMLIdentityProvider.class);
     public SAMLIdentityProvider(SAMLIdentityProviderConfig config) {
         super(config);
     }
@@ -141,25 +149,56 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
     }
 
     @Override
+    public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
+        String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
+        if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
+        SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
+        try {
+            int status = SimpleHttp.doPost(singleLogoutServiceUrl)
+                    .param(GeneralConstants.SAML_REQUEST_KEY, logoutBuilder.postBinding().encoded())
+                    .param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
+            boolean success = status >=200 && status < 400;
+            if (!success) {
+                logger.warn("Failed saml backchannel broker logout to: " + singleLogoutServiceUrl);
+            }
+        } catch (Exception e) {
+            logger.warn("Failed saml backchannel broker logout to: " + singleLogoutServiceUrl, e);
+        }
+
+    }
+
+    @Override
     public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
-        if (getConfig().getSingleLogoutServiceUrl() == null || getConfig().getSingleLogoutServiceUrl().trim().equals("")) return null;
+        String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
+        if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("")) return null;
+
+        if (getConfig().isBackchannelSupported()) {
+            backchannelLogout(userSession, uriInfo, realm);
+            return null;
+       } else {
+            try {
+                SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
+                return logoutBuilder.postBinding().request();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+    }
 
+    protected SAML2LogoutRequestBuilder buildLogoutRequest(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm, String singleLogoutServiceUrl) {
         SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
                 .assertionExpiration(realm.getAccessCodeLifespan())
                 .issuer(getEntityId(uriInfo, realm))
                 .sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
                 .userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
-                .destination(getConfig().getSingleLogoutServiceUrl());
+                .destination(singleLogoutServiceUrl)
+                .relayState(userSession.getId());
         if (getConfig().isWantAuthnRequestsSigned()) {
             logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
                     .signDocument();
         }
-        try {
-            return logoutBuilder.relayState(userSession.getId()).postBinding().request();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
+        return logoutBuilder;
     }
 
     @Override
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index 10ef464..ad11be3 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -110,4 +110,13 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
     public void setPostBindingResponse(boolean postBindingResponse) {
         getConfig().put("postBindingResponse", String.valueOf(postBindingResponse));
     }
+
+    public boolean isBackchannelSupported() {
+        return Boolean.valueOf(getConfig().get("backchannelSupported"));
+    }
+
+    public void setBackchannelSupported(boolean backchannel) {
+        getConfig().put("backchannelSupported", String.valueOf(backchannel));
+    }
+
 }
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index 6ae4a81..9d23164 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -42,7 +42,7 @@
                         <span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group">
-                        <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+                        <label class="col-sm-2 control-label" for="storeToken">Store Tokens</label>
                         <div class="col-sm-4">
                             <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
                         </div>
@@ -93,6 +93,13 @@
                         </div>
                         <span tooltip-placement="right" tooltip="End session endpoint to use to logout user from external IDP." class="fa fa-info-circle"></span>
                     </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="backchannelSupported">Backchannel Logout</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" value="'true'" onoffswitchvalue />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Does the external IDP support backchannel logout?" class="fa fa-info-circle"></span>
+                    </div>
                     <div class="form-group clearfix">
                         <label class="col-sm-2 control-label" for="userInfoUrl">User Info Url</label>
                         <div class="col-sm-4">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
index 6746d0a..05f4c63 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
@@ -42,7 +42,7 @@
                         <span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group">
-                        <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+                        <label class="col-sm-2 control-label" for="storeToken">Store Tokens</label>
                         <div class="col-sm-4">
                             <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
                         </div>
@@ -87,6 +87,13 @@
                         </div>
                         <span tooltip-placement="right" tooltip="The Url that must be used to send logout requests." class="fa fa-info-circle"></span>
                     </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="backchannelSupported">Backchannel Logout</label>
+                        <div class="col-sm-4">
+                            <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" value="'true'" onoffswitchvalue />
+                        </div>
+                        <span tooltip-placement="right" tooltip="Does the external IDP support backchannel logout?" class="fa fa-info-circle"></span>
+                    </div>
                     <div class="form-group clearfix">
                         <label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
                         <div class="col-sm-4">
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java
index 8143d15..7fe2fb3 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java
@@ -39,6 +39,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
  */
 public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
     protected static final Logger logger = Logger.getLogger(SAML2BindingBuilder.class);
+    public static final String RELAY_STATE = "RelayState";
 
     protected KeyPair signingKeyPair;
     protected X509Certificate signingCertificate;
@@ -328,7 +329,7 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
         builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
 
         if (isNotNull(relayState)) {
-            builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + escapeAttribute(relayState) + "\"/>");
+            builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + RELAY_STATE + "\" " + "VALUE=\"" + escapeAttribute(relayState) + "\"/>");
         }
 
         builder.append("<NOSCRIPT>");
@@ -355,7 +356,7 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
                 .replaceQuery(null)
                 .queryParam(samlParameterName, base64Encoded(document));
         if (relayState != null) {
-            builder.queryParam("RelayState", relayState);
+            builder.queryParam(RELAY_STATE, relayState);
         }
 
         if (sign) {
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 315e685..f650b64 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -161,7 +161,7 @@ public class SamlProtocol implements LoginProtocol {
         return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || forcePostBinding(client);
     }
 
-    protected boolean isLogoutPostBindingForInitiator(UserSessionModel session) {
+    public static boolean isLogoutPostBindingForInitiator(UserSessionModel session) {
         String note = session.getNote(SamlProtocol.SAML_LOGOUT_BINDING);
         return SamlProtocol.SAML_POST_BINDING.equals(note);
     }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index ff936e5..d11ffb0 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -6,6 +6,12 @@ import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.ClientConnection;
 import org.keycloak.VerificationException;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.NameIDPolicyType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -18,22 +24,18 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.services.ErrorPage;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.HttpAuthenticationManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.RealmsResource;
-import org.keycloak.services.ErrorPage;
 import org.keycloak.util.StreamUtil;
-import org.keycloak.saml.common.constants.GeneralConstants;
-import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
-import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
-import org.keycloak.dom.saml.v2.SAML2Object;
-import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
-import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
-import org.keycloak.dom.saml.v2.protocol.NameIDPolicyType;
-import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
-import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
@@ -51,6 +53,7 @@ import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.security.PublicKey;
@@ -122,7 +125,7 @@ public class SamlService {
         protected Response handleSamlResponse(String samlResponse, String relayState) {
             event.event(EventType.LOGOUT);
             SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
-            StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
+            StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
             // validate destination
             if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
                 event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
@@ -162,7 +165,7 @@ public class SamlService {
 
             SAML2Object samlObject = documentHolder.getSamlObject();
 
-            RequestAbstractType requestAbstractType = (RequestAbstractType)samlObject;
+            RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject;
             String issuer = requestAbstractType.getIssuer().getValue();
             ClientModel client = realm.getClientByClientId(issuer);
 
@@ -177,7 +180,7 @@ public class SamlService {
                 event.error(Errors.CLIENT_DISABLED);
                 return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
             }
-            if ((client instanceof ClientModel) && ((ClientModel)client).isBearerOnly()) {
+            if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.NOT_ALLOWED);
                 return ErrorPage.error(session, Messages.BEARER_ONLY);
@@ -221,6 +224,7 @@ public class SamlService {
         protected abstract void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException;
 
         protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
+
         protected abstract SAMLDocumentHolder extractResponseDocument(String response);
 
         protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
@@ -231,7 +235,8 @@ public class SamlService {
                 return ErrorPage.error(session, Messages.INVALID_REQUEST);
             }
             String bindingType = getBindingType(requestAbstractType);
-            if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) bindingType = SamlProtocol.SAML_POST_BINDING;
+            if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING)))
+                bindingType = SamlProtocol.SAML_POST_BINDING;
             String redirect = null;
             URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
             if (redirectUri != null && !"null".equals(redirectUri)) {  // "null" is for testing purposes
@@ -243,7 +248,7 @@ public class SamlService {
                     redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
                 }
                 if (redirect == null && client instanceof ClientModel) {
-                    redirect = ((ClientModel)client).getManagementUrl();
+                    redirect = ((ClientModel) client).getManagementUrl();
                 }
 
             }
@@ -265,10 +270,10 @@ public class SamlService {
 
             // Handle NameIDPolicy from SP
             NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy();
-            if(nameIdPolicy != null && !SamlProtocol.forceNameIdFormat(client)) {
+            if (nameIdPolicy != null && !SamlProtocol.forceNameIdFormat(client)) {
                 String nameIdFormat = nameIdPolicy.getFormat().toString();
                 // TODO: Handle AllowCreate too, relevant for persistent NameID.
-                if(isSupportedNameIdFormat(nameIdFormat)) {
+                if (isSupportedNameIdFormat(nameIdFormat)) {
                     clientSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
                 } else {
                     event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
@@ -344,7 +349,8 @@ public class SamlService {
             AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
             if (authResult != null) {
                 String logoutBinding = getBindingType();
-                if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) logoutBinding = SamlProtocol.SAML_POST_BINDING;
+                if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING)))
+                    logoutBinding = SamlProtocol.SAML_POST_BINDING;
                 String bindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
                 UserSessionModel userSession = authResult.getSession();
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
@@ -364,33 +370,51 @@ public class SamlService {
                 }
                 logger.debug("browser Logout");
                 return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
-            }
-
+            } else if (logoutRequest.getSessionIndex() != null) {
+                for (String sessionIndex : logoutRequest.getSessionIndex()) {
+                    ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
+                    if (clientSession == null) continue;
+                    if (clientSession.getClient().getClientId().equals(client.getClientId())) {
+                        // remove requesting client from logout
+                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
+                    }
+                    UserSessionModel userSession = clientSession.getUserSession();
+                    try {
+                        authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
+                    } catch (Exception e) {
+                        logger.warn("Failure with backchannel logout", e);
+                    }
 
-            String redirectUri = null;
+                }
 
-            if (client instanceof ClientModel) {
-                redirectUri = ((ClientModel)client).getBaseUrl();
             }
 
-            if (redirectUri != null) {
-                redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
-                if (redirectUri == null) {
-                    return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
-                }
+            // default
+
+            String logoutBinding = getBindingType();
+            String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
+            String logoutRelayState = relayState;
+            SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+            builder.logoutRequestID(logoutRequest.getID());
+            builder.destination(logoutBindingUri);
+            builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
+            builder.relayState(logoutRelayState);
+            if (SamlProtocol.requiresRealmSignature(client)) {
+                SignatureAlgorithm algorithm = SamlProtocol.getSignatureAlgorithm(client);
+                builder.signatureAlgorithm(algorithm)
+                        .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+                        .signDocument();
+
             }
-            if (redirectUri != null) {
-                return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
-            } else {
-                return Response.ok().build();
+            try {
+                if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
+                    return builder.postBinding().response(logoutBindingUri);
+                } else {
+                    return builder.redirectBinding().response(logoutBindingUri);
+                }
+            } catch (Exception e) {
+                throw new RuntimeException(e);
             }
-
-        }
-
-        private Response logout(UserSessionModel userSession) {
-            Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
-            if (response == null) event.user(userSession.getUser()).session(userSession).success();
-            return response;
         }
 
         private boolean checkSsl() {
@@ -414,6 +438,7 @@ public class SamlService {
         protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
             return SAMLRequestParser.parseRequestPostBinding(samlRequest);
         }
+
         @Override
         protected SAMLDocumentHolder extractResponseDocument(String response) {
             return SAMLRequestParser.parseResponsePostBinding(response);
@@ -446,7 +471,6 @@ public class SamlService {
         }
 
 
-
         @Override
         protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
             return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
@@ -478,7 +502,7 @@ public class SamlService {
     @GET
     public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
                                     @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
-                                    @QueryParam(GeneralConstants.RELAY_STATE) String relayState)  {
+                                    @QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
         logger.debug("SAML GET");
         return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 31cdca3..efc2f46 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -126,7 +126,7 @@ public class LogoutEndpoint {
             return AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers);
         } else if (userSession != null) { // non browser logout
             event.event(EventType.LOGOUT);
-            authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
             event.user(userSession.getUser()).session(userSession).success();
         }
 
@@ -183,7 +183,7 @@ public class LogoutEndpoint {
     }
 
     private void logout(UserSessionModel userSession) {
-        authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+        authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
         event.user(userSession.getUser()).session(userSession).success();
     }
 
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 f77fbdd..b728607 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -86,7 +86,7 @@ public class TokenManager {
 
         UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
         ClientSessionModel clientSession = null;
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 6a96037..2bb4d8f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -104,7 +104,20 @@ public class AuthenticationManager {
 
     }
 
-    public static void backchannelLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
+    /**
+     * Do not logout broker
+     *
+     * @param session
+     * @param realm
+     * @param userSession
+     * @param uriInfo
+     * @param connection
+     * @param headers
+     */
+    public static void backchannelLogout(KeycloakSession session, RealmModel realm,
+                                         UserSessionModel userSession, UriInfo uriInfo,
+                                         ClientConnection connection, HttpHeaders headers,
+                                         boolean logoutBroker) {
         if (userSession == null) return;
         UserModel user = userSession.getUser();
         userSession.setState(UserSessionModel.State.LOGGING_OUT);
@@ -115,6 +128,16 @@ public class AuthenticationManager {
         for (ClientSessionModel clientSession : userSession.getClientSessions()) {
             backchannelLogoutClientSession(session, realm, clientSession, userSession, uriInfo, headers);
         }
+        if (logoutBroker) {
+            String brokerId = userSession.getNote(IdentityBrokerService.BROKER_PROVIDER_ID);
+            if (brokerId != null) {
+                IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
+                try {
+                    identityProvider.backchannelLogout(userSession, uriInfo, realm);
+                } catch (Exception e) {
+                }
+            }
+        }
         userSession.setState(UserSessionModel.State.LOGGED_OUT);
         session.sessions().removeUserSession(realm, userSession);
     }
@@ -131,8 +154,8 @@ public class AuthenticationManager {
             protocol.backchannelLogout(userSession, clientSession);
             clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
         }
-    }
 
+    }
 
     public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         if (userSession == null) return null;
@@ -525,7 +548,7 @@ public class AuthenticationManager {
 
             UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
             if (!isSessionValid(realm, userSession)) {
-                if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
+                if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
                 logger.debug("User session not active");
                 return null;
             }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index bc81f46..207c5b7 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -480,7 +480,7 @@ public class AccountService {
         UserModel user = auth.getUser();
         List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
         for (UserSessionModel userSession : userSessions) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
         }
 
         UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "sessionsPage");
@@ -675,7 +675,7 @@ public class AccountService {
         List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
         for (UserSessionModel s : sessions) {
             if (!s.getId().equals(auth.getSession().getId())) {
-                AuthenticationManager.backchannelLogout(session, realm, s, uriInfo, clientConnection, headers);
+                AuthenticationManager.backchannelLogout(session, realm, s, uriInfo, clientConnection, headers, true);
             }
         }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 6031c2e..129ee5e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -280,7 +280,7 @@ public class RealmAdminResource {
     public void deleteSession(@PathParam("session") String sessionId) {
         UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
         if (userSession == null) throw new NotFoundException("Sesssion not found");
-        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
+        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
     }
 
     /**
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 20aeaa5..49abc1d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -379,7 +379,7 @@ public class UsersResource {
 
         List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
         for (UserSessionModel userSession : userSessions) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
         }
     }
 
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 aeb3e2a..4704943 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -595,7 +595,7 @@ public class LoginActionsService {
         }
 
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
             event.error(Errors.INVALID_CODE);
             return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
         }
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
index cc9364f..ffce087 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
@@ -3,7 +3,8 @@ package org.keycloak.social.facebook;
 import org.codehaus.jackson.JsonNode;
 import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
 import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.social.SocialIdentityProvider;
@@ -27,7 +28,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider imp
 
     protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
         try {
-            JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
+            JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
 
             String id = getJsonProperty(profile, "id");
 
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
index ec56d15..d9db0e9 100755
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
@@ -3,7 +3,8 @@ package org.keycloak.social.github;
 import org.codehaus.jackson.JsonNode;
 import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
 import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.social.SocialIdentityProvider;
@@ -28,7 +29,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
     @Override
     protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
         try {
-            JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
+            JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
 
             BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
 
diff --git a/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java b/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java
index 911a55c..c031443 100755
--- a/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java
+++ b/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java
@@ -25,7 +25,8 @@ import org.codehaus.jackson.JsonNode;
 import org.jboss.logging.Logger;
 import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
 import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.social.SocialIdentityProvider;
@@ -55,7 +56,7 @@ public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider imp
 	protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
 		log.debug("doGetFederatedIdentity()");
 		try {
-			JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
+			JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
 
             BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
 
diff --git a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
index f834464..3e9cc8c 100755
--- a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
+++ b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
@@ -26,7 +26,8 @@ import java.util.HashMap;
 import org.codehaus.jackson.JsonNode;
 import org.jboss.logging.Logger;
 import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
-import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.social.SocialIdentityProvider;
@@ -64,7 +65,7 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
 			if (log.isDebugEnabled()) {
 				log.debug("StackOverflow profile request to: " + URL);
 			}
-			JsonNode profile = SimpleHttp.doGet(URL).asJson().get("items").get(0);
+			JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL)).get("items").get(0);
 
             BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));
 
diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
index 50e45b9..009fda5 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
@@ -120,7 +120,8 @@
                 "forceAuthn": true,
                 "validateSignature": true,
                 "postBindingResponse": true,
-                "postBindingAuthnRequest": true
+                "postBindingAuthnRequest": true,
+                "backchannelSupported": true
             }
         },
         {
@@ -169,7 +170,8 @@
                 "tokenUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token",
                 "userInfoUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo",
                 "logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/logout",
-                "defaultScope": "email profile"
+                "defaultScope": "email profile",
+                "backchannelSupported": "true"
             }
         }
     ],