Java Code Examples for org.keycloak.models.ClientModel#isEnabled()

The following examples show how to use org.keycloak.models.ClientModel#isEnabled() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: LoginStatusIframeEndpoint.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@GET
@Path("init")
public Response preCheck(@QueryParam("client_id") String clientId, @QueryParam("origin") String origin) {
    try {
        UriInfo uriInfo = session.getContext().getUri();
        RealmModel realm = session.getContext().getRealm();
        ClientModel client = session.realms().getClientByClientId(clientId, realm);
        if (client != null && client.isEnabled()) {
            Set<String> validWebOrigins = WebOriginsUtils.resolveValidWebOrigins(session, client);
            validWebOrigins.add(UriUtils.getOrigin(uriInfo.getRequestUri()));
            if (validWebOrigins.contains("*") || validWebOrigins.contains(origin)) {
                return Response.noContent().build();
            }
        }
    } catch (Throwable t) {
    }
    return Response.status(Response.Status.FORBIDDEN).build();
}
 
Example 2
Source File: ResourceAdminManager.java    From keycloak with Apache License 2.0 6 votes vote down vote up
protected GlobalRequestResult logoutClient(RealmModel realm, ClientModel resource, int notBefore) {

        if (!resource.isEnabled()) {
            return new GlobalRequestResult();
        }

        List<String> mgmtUrls = getAllManagementUrls(resource);
        if (mgmtUrls.isEmpty()) {
            logger.debug("No management URL or no registered cluster nodes for the client " + resource.getClientId());
            return new GlobalRequestResult();
        }

        if (logger.isDebugEnabled()) logger.debug("Send logoutClient for URLs: " + mgmtUrls);

        // Propagate this to all hosts
        GlobalRequestResult result = new GlobalRequestResult();
        for (String mgmtUrl : mgmtUrls) {
            if (sendLogoutRequest(realm, resource, null, null, notBefore, mgmtUrl)) {
                result.addSuccessRequest(mgmtUrl);
            } else {
                result.addFailedRequest(mgmtUrl);
            }
        }
        return result;
    }
 
Example 3
Source File: TokenManager.java    From keycloak with Apache License 2.0 5 votes vote down vote up
/**
 * Checks if the token is valid. Intended usage is for token introspection endpoints as the session last refresh
 * is updated if the token was valid. This is used to keep the session alive when long lived tokens are used.
 *
 * @param session
 * @param realm
 * @param token
 * @return
 * @throws OAuthErrorException
 */
public boolean checkTokenValidForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token) throws OAuthErrorException {
    ClientModel client = realm.getClientByClientId(token.getIssuedFor());
    if (client == null || !client.isEnabled()) {
        return false;
    }

    try {
        TokenVerifier.createWithoutSignature(token)
                .withChecks(NotBeforeCheck.forModel(client), TokenVerifier.IS_ACTIVE)
                .verify();
    } catch (VerificationException e) {
        return false;
    }

    boolean valid = false;

    UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), false, client.getId());

    if (AuthenticationManager.isSessionValid(realm, userSession)) {
        valid = isUserValid(session, realm, token, userSession);
    } else {
        userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), true, client.getId());
        if (AuthenticationManager.isOfflineSessionValid(realm, userSession)) {
            valid = isUserValid(session, realm, token, userSession);
        }
    }

    if (valid) {
        userSession.setLastSessionRefresh(Time.currentTime());
    }

    return valid;
}
 
Example 4
Source File: RedirectUtils.java    From keycloak with Apache License 2.0 5 votes vote down vote up
private static Set<String> getValidateRedirectUris(KeycloakSession session) {
    Set<String> redirects = new HashSet<>();
    for (ClientModel client : session.getContext().getRealm().getClients()) {
        if (client.isEnabled()) {
            redirects.addAll(resolveValidRedirects(session, client.getRootUrl(), client.getRedirectUris()));
        }
    }
    return redirects;
}
 
Example 5
Source File: SamlService.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@GET
@Path("clients/{client}")
@Produces(MediaType.TEXT_HTML_UTF_8)
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
    event.event(EventType.LOGIN);
    CacheControlUtil.noBackButtonCacheControlHeader();
    ClientModel client = null;
    for (ClientModel c : realm.getClients()) {
        String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME);
        if (urlName == null)
            continue;
        if (urlName.equals(clientUrlName)) {
            client = c;
            break;
        }
    }
    if (client == null) {
        event.error(Errors.CLIENT_NOT_FOUND);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.CLIENT_NOT_FOUND);
    }
    if (!client.isEnabled()) {
        event.error(Errors.CLIENT_DISABLED);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.CLIENT_DISABLED);
    }
    if (!isClientProtocolCorrect(client)) {
        event.error(Errors.INVALID_CLIENT);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol.");
    }

    session.getContext().setClient(client);

    AuthenticationSessionModel authSession = getOrCreateLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
    if (authSession == null) {
        logger.error("SAML assertion consumer url not set up");
        event.error(Errors.INVALID_REDIRECT_URI);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REDIRECT_URI);
    }

    return newBrowserAuthentication(authSession, false, false);
}
 
Example 6
Source File: SamlService.java    From keycloak with Apache License 2.0 4 votes vote down vote up
protected Response handleSamlRequest(String samlRequest, String relayState) {
    SAMLDocumentHolder documentHolder = extractRequestDocument(samlRequest);
    if (documentHolder == null) {
        event.event(EventType.LOGIN);
        event.error(Errors.INVALID_TOKEN);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
    }

    SAML2Object samlObject = documentHolder.getSamlObject();

    if (samlObject instanceof AuthnRequestType) {
        logger.debug("** login request");
        event.event(EventType.LOGIN);
    } else if (samlObject instanceof LogoutRequestType) {
        logger.debug("** logout request");
        event.event(EventType.LOGOUT);
    } else {
        event.event(EventType.LOGIN);
        event.error(Errors.INVALID_TOKEN);
        event.detail(Details.REASON, "Unhandled SAML document type: " + (samlObject == null ? "<null>" : samlObject.getClass().getSimpleName()));
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
    }

    RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject;
    final NameIDType issuerNameId = requestAbstractType.getIssuer();
    String issuer = requestAbstractType.getIssuer() == null ? null : issuerNameId.getValue();
    ClientModel client = realm.getClientByClientId(issuer);

    if (client == null) {
        event.client(issuer);
        event.error(Errors.CLIENT_NOT_FOUND);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.UNKNOWN_LOGIN_REQUESTER);
    }

    if (!client.isEnabled()) {
        event.error(Errors.CLIENT_DISABLED);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.LOGIN_REQUESTER_NOT_ENABLED);
    }
    if (client.isBearerOnly()) {
        event.error(Errors.NOT_ALLOWED);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.BEARER_ONLY);
    }
    if (!client.isStandardFlowEnabled()) {
        event.error(Errors.NOT_ALLOWED);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.STANDARD_FLOW_DISABLED);
    }
    if (!isClientProtocolCorrect(client)) {
        event.error(Errors.INVALID_CLIENT);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol.");
    }

    session.getContext().setClient(client);

    SamlClient samlClient = new SamlClient(client);
    try {
        if (samlClient.requiresClientSignature()) {
            verifySignature(documentHolder, client);
        }
    } catch (VerificationException e) {
        SamlService.logger.error("request validation failed", e);
        event.error(Errors.INVALID_SIGNATURE);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
    }
    logger.debug("verified request");

    if (requestAbstractType.getDestination() == null && containsUnencryptedSignature(documentHolder)) {
        event.detail(Details.REASON, "missing_required_destination");
        event.error(Errors.INVALID_REQUEST);
        return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
    }

    if (samlObject instanceof AuthnRequestType) {
        // Get the SAML Request Message
        AuthnRequestType authn = (AuthnRequestType) samlObject;
        return loginRequest(relayState, authn, client);
    } else if (samlObject instanceof LogoutRequestType) {
        LogoutRequestType logout = (LogoutRequestType) samlObject;
        return logoutRequest(logout, client, relayState);
    } else {
        throw new IllegalStateException("Invalid SAML object");
    }
}
 
Example 7
Source File: X509ClientAuthenticator.java    From keycloak with Apache License 2.0 4 votes vote down vote up
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {

    X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
    if (provider == null) {
        logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?",
                X509ClientCertificateLookup.class);
        return;
    }

    X509Certificate[] certs = null;
    ClientModel client = null;
    try {
        certs = provider.getCertificateChain(context.getHttpRequest());
        String client_id = null;
        MediaType mediaType = context.getHttpRequest().getHttpHeaders().getMediaType();
        boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);

        MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
        MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();

        if (formData != null) {
            client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
        }

        if (client_id == null && queryParams != null) {
            client_id = queryParams.getFirst(OAuth2Constants.CLIENT_ID);
        }

        if (client_id == null) {
            client_id = context.getSession().getAttribute("client_id", String.class);
        }

        if (client_id == null) {
            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
            context.challenge(challengeResponse);
            return;
        }

        client = context.getRealm().getClientByClientId(client_id);
        if (client == null) {
            context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
            return;
        }
        context.getEvent().client(client_id);
        context.setClient(client);

        if (!client.isEnabled()) {
            context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
            return;
        }
    } catch (GeneralSecurityException e) {
        logger.errorf("[X509ClientCertificateAuthenticator:authenticate] Exception: %s", e.getMessage());
        context.attempted();
        return;
    }

    if (certs == null || certs.length == 0) {
        // No x509 client cert, fall through and
        // continue processing the rest of the authentication flow
        logger.debug("[X509ClientCertificateAuthenticator:authenticate] x509 client certificate is not available for mutual SSL.");
        context.attempted();
        return;
    }

    String subjectDNRegexp = client.getAttribute(ATTR_SUBJECT_DN);
    if (subjectDNRegexp == null || subjectDNRegexp.length() == 0) {
        logger.errorf("[X509ClientCertificateAuthenticator:authenticate] " + ATTR_SUBJECT_DN + " is null or empty");
        context.attempted();
        return;
    }
    Pattern subjectDNPattern = Pattern.compile(subjectDNRegexp);

    Optional<String> matchedCertificate = Arrays.stream(certs)
          .map(certificate -> certificate.getSubjectDN().getName())
          .filter(subjectdn -> subjectDNPattern.matcher(subjectdn).matches())
          .findFirst();

    if (!matchedCertificate.isPresent()) {
        // We do quite expensive operation here, so better check the logging level beforehand.
        if (logger.isDebugEnabled()) {
            logger.debug("[X509ClientCertificateAuthenticator:authenticate] Couldn't match any certificate for pattern " + subjectDNRegexp);
            logger.debug("[X509ClientCertificateAuthenticator:authenticate] Available SubjectDNs: " +
                  Arrays.stream(certs)
                        .map(cert -> cert.getSubjectDN().getName())
                        .collect(Collectors.toList()));
        }
        context.attempted();
        return;
    } else {
        logger.debug("[X509ClientCertificateAuthenticator:authenticate] Matched " + matchedCertificate.get() + " certificate.");
    }

    context.success();
}
 
Example 8
Source File: UserResource.java    From keycloak with Apache License 2.0 4 votes vote down vote up
/**
 * Send a update account email to the user
 *
 * An email contains a link the user can click to perform a set of required actions.
 * The redirectUri and clientId parameters are optional. If no redirect is given, then there will
 * be no link back to click after actions have completed.  Redirect uri must be a valid uri for the
 * particular clientId.
 *
 * @param redirectUri Redirect uri
 * @param clientId Client id
 * @param lifespan Number of seconds after which the generated token expires
 * @param actions required actions the user needs to complete
 * @return
 */
@Path("execute-actions-email")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response executeActionsEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
                                    @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
                                    @QueryParam("lifespan") Integer lifespan,
                                    List<String> actions) {
    auth.users().requireManage(user);

    if (user.getEmail() == null) {
        return ErrorResponse.error("User email missing", Status.BAD_REQUEST);
    }

    if (!user.isEnabled()) {
        throw new WebApplicationException(
            ErrorResponse.error("User is disabled", Status.BAD_REQUEST));
    }

    if (redirectUri != null && clientId == null) {
        throw new WebApplicationException(
            ErrorResponse.error("Client id missing", Status.BAD_REQUEST));
    }

    if (clientId == null) {
        clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
    }

    ClientModel client = realm.getClientByClientId(clientId);
    if (client == null) {
        logger.debugf("Client %s doesn't exist", clientId);
        throw new WebApplicationException(
            ErrorResponse.error("Client doesn't exist", Status.BAD_REQUEST));
    }
    if (!client.isEnabled()) {
        logger.debugf("Client %s is not enabled", clientId);
        throw new WebApplicationException(
                ErrorResponse.error("Client is not enabled", Status.BAD_REQUEST));
    }

    String redirect;
    if (redirectUri != null) {
        redirect = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
        if (redirect == null) {
            throw new WebApplicationException(
                ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));
        }
    }

    if (lifespan == null) {
        lifespan = realm.getActionTokenGeneratedByAdminLifespan();
    }
    int expiration = Time.currentTime() + lifespan;
    ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), expiration, actions, redirectUri, clientId);

    try {
        UriBuilder builder = LoginActionsService.actionTokenProcessor(session.getContext().getUri());
        builder.queryParam("key", token.serialize(session, realm, session.getContext().getUri()));

        String link = builder.build(realm.getName()).toString();

        this.session.getProvider(EmailTemplateProvider.class)
          .setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions())
          .setRealm(realm)
          .setUser(user)
          .sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(lifespan));

        //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();

        adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).success();

        return Response.noContent().build();
    } catch (EmailException e) {
        ServicesLogger.LOGGER.failedToSendActionsEmail(e);
        return ErrorResponse.error("Failed to send execute actions email", Status.INTERNAL_SERVER_ERROR);
    }
}
 
Example 9
Source File: SessionCodeChecks.java    From keycloak with Apache License 2.0 4 votes vote down vote up
public boolean initialVerify() {
    // Basic realm checks and authenticationSession retrieve
    authSession = initialVerifyAuthSession();
    if (authSession == null) {
        return false;
    }

    // Check cached response from previous action request
    response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
    if (response != null) {
        return false;
    }

    // Client checks
    event.detail(Details.CODE_ID, authSession.getParentSession().getId());
    ClientModel client = authSession.getClient();
    if (client == null) {
        event.error(Errors.CLIENT_NOT_FOUND);
        response = ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.UNKNOWN_LOGIN_REQUESTER);
        clientCode.removeExpiredClientSession();
        return false;
    }

    event.client(client);
    session.getContext().setClient(client);

    if (!client.isEnabled()) {
        event.error(Errors.CLIENT_DISABLED);
        response = ErrorPage.error(session,authSession, Response.Status.BAD_REQUEST, Messages.LOGIN_REQUESTER_NOT_ENABLED);
        clientCode.removeExpiredClientSession();
        return false;
    }


    // Check if it's action or not
    if (code == null) {
        String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
        String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);

        // Check if we transitted between flows (eg. clicking "register" on login screen)
        if (execution==null && !flowPath.equals(lastFlow)) {
            logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);

            // Don't allow moving to different flow if I am on requiredActions already
            if (AuthenticationSessionModel.Action.AUTHENTICATE.name().equals(authSession.getAction())) {
                authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
                authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
                lastExecFromSession = null;
            }
        }

        if (execution == null || execution.equals(lastExecFromSession)) {
            // Allow refresh of previous page
            clientCode = new ClientSessionCode<>(session, realm, authSession);
            actionRequest = false;

            // Allow refresh, but rewrite browser history
            if (execution == null && lastExecFromSession != null) {
                logger.debugf("Parameter 'execution' is not in the request, but flow wasn't changed. Will update browser history");
                request.setAttribute(BrowserHistoryHelper.SHOULD_UPDATE_BROWSER_HISTORY, true);
            }

            return true;
        } else {
            response = showPageExpired(authSession);
            return false;
        }
    } else {
        ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, tabId, session, realm, client, event, authSession);
        clientCode = result.getCode();
        if (clientCode == null) {

            // In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
            if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
                String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
                URI redirectUri = getLastExecutionUrl(latestFlowPath, execution, tabId);

                logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
                authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
                response = Response.status(Response.Status.FOUND).location(redirectUri).build();
            } else {
                response = showPageExpired(authSession);
            }
            return false;
        }


        actionRequest = true;
        if (execution != null) {
            authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
        }
        return true;
    }
}
 
Example 10
Source File: AccountLoader.java    From keycloak with Apache License 2.0 4 votes vote down vote up
public Object getAccountService(KeycloakSession session, EventBuilder event) {
    RealmModel realm = session.getContext().getRealm();

    ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
    if (client == null || !client.isEnabled()) {
        logger.debug("account management not enabled");
        throw new NotFoundException("account management not enabled");
    }

    HttpRequest request = session.getContext().getContextObject(HttpRequest.class);
    HttpHeaders headers = session.getContext().getRequestHeaders();
    MediaType content = headers.getMediaType();
    List<MediaType> accepts = headers.getAcceptableMediaTypes();

    Theme theme = getTheme(session);
    boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
    UriInfo uriInfo = session.getContext().getUri();

    if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
        return new CorsPreflightService(request);
    } else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !uriInfo.getPath().endsWith("keycloak.json")) {
        AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
        if (authResult == null) {
            throw new NotAuthorizedException("Bearer token required");
        }

        if (authResult.getUser().getServiceAccountClientLink() != null) {
            throw new NotAuthorizedException("Service accounts are not allowed to access this service");
        }

        Auth auth = new Auth(session.getContext().getRealm(), authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false);
        AccountRestService accountRestService = new AccountRestService(session, auth, client, event);
        ResteasyProviderFactory.getInstance().injectProperties(accountRestService);
        accountRestService.init();
        return accountRestService;
    } else {
        if (deprecatedAccount) {
            AccountFormService accountFormService = new AccountFormService(realm, client, event);
            ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
            accountFormService.init();
            return accountFormService;
        } else {
            AccountConsole console = new AccountConsole(realm, client, theme);
            ResteasyProviderFactory.getInstance().injectProperties(console);
            console.init();
            return console;
        }
    }
}
 
Example 11
Source File: AuthenticationManager.java    From keycloak with Apache License 2.0 4 votes vote down vote up
/**
 * Logs out the given client session and records the result into {@code logoutAuthSession} if set.
 * @param session
 * @param realm
 * @param clientSession
 * @param logoutAuthSession auth session used for recording result of logout. May be {@code null}
 * @param uriInfo
 * @param headers
 * @return {@code true} if the client was or is already being logged out, {@code false} if logout failed or it is not known how to log it out.
 */
private static boolean backchannelLogoutClientSession(KeycloakSession session, RealmModel realm,
  AuthenticatedClientSessionModel clientSession, AuthenticationSessionModel logoutAuthSession,
  UriInfo uriInfo, HttpHeaders headers) {
    UserSessionModel userSession = clientSession.getUserSession();
    ClientModel client = clientSession.getClient();

    if (client.isFrontchannelLogout() || AuthenticationSessionModel.Action.LOGGED_OUT.name().equals(clientSession.getAction())) {
        return false;
    }

    final AuthenticationSessionModel.Action logoutState = getClientLogoutAction(logoutAuthSession, client.getId());

    if (logoutState == AuthenticationSessionModel.Action.LOGGED_OUT || logoutState == AuthenticationSessionModel.Action.LOGGING_OUT) {
        return true;
    }

    if (!client.isEnabled()) {
        return false;
    }

    try {
        setClientLogoutAction(logoutAuthSession, client.getId(), AuthenticationSessionModel.Action.LOGGING_OUT);

        String authMethod = clientSession.getProtocol();
        if (authMethod == null) return true; // must be a keycloak service like account

        logger.debugv("backchannel logout to: {0}", client.getClientId());
        LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
        protocol.setRealm(realm)
                .setHttpHeaders(headers)
                .setUriInfo(uriInfo);
        protocol.backchannelLogout(userSession, clientSession);

        setClientLogoutAction(logoutAuthSession, client.getId(), AuthenticationSessionModel.Action.LOGGED_OUT);

        return true;
    } catch (Exception ex) {
        ServicesLogger.LOGGER.failedToLogoutClient(ex);
        return false;
    }
}