Zum Hauptinhalt springen

Sign users out of all applications

This guide explains how to sign a user out of every application of an organization with one request, and how your applications learn about the sign-out so that they can end their own sessions.


Learning outcomes​

  • Call the SSO logout endpoint for all sessions or for the current session only.
  • Receive and verify sign-out notifications in your applications.
  • Receive OpenID Connect (OIDC) back-channel logout tokens.
  • Find the cause when a user stays signed in.

What you need​


About single sign-out​

Single sign-out ends the sessions of a user in Casdoor and tells your applications to do the same. Typical uses are:

  • Security incidents: End all sessions of an account at once.
  • Organization policy: Sign users out everywhere when they leave the organization or change roles.
  • Compliance: Guarantee that a user is signed out of all systems.
  • User request: Let users sign out of all applications with one action.

The logoutAll parameter selects one of two modes:

Full sign-out: logoutAll=true (default)Current session only: logoutAll=false
SessionsDeletes all sessions of the user across all applications of the organizationDeletes the current session
Access tokensExpires all access tokens issued to the userClears the token of the current session
NotificationContains all session IDs and the hashes of all expired tokensContains the current session ID and the hashes of its tokens

Use logoutAll=false when a user signs out on one device or browser and stays signed in on the others.

This endpoint is different from RP-initiated logout, the standard OIDC end-session endpoint that a single client calls for its own session.

Call the SSO logout endpoint​

GET or POST /api/sso-logout?logoutAll=<true|false>
ParameterRequiredDescription
logoutAllNotrue, 1, or omitted: sign out of all sessions. Any other value, such as false or 0: sign out of the current session only

Authenticate the request as the user, with the access token in the Authorization header or with the session cookie. See Choose an authentication method.

Sign out of all sessions:

curl -X POST https://door.casdoor.com/api/sso-logout \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Or explicitly specify logoutAll=true
curl -X POST "https://door.casdoor.com/api/sso-logout?logoutAll=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Sign out of the current session only:

curl -X POST "https://door.casdoor.com/api/sso-logout?logoutAll=false" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Authenticate with the session cookie:

curl -X POST https://door.casdoor.com/api/sso-logout \
--cookie "casdoor_session_id=abc123def456"

A successful request returns:

{
"status": "ok",
"msg": "",
"data": ""
}
FieldDescription
status"ok" on success, "error" on failure
msgError message on failure, otherwise empty
dataEmpty for this endpoint

Call the endpoint from your application​

In a browser, with fetch:

// Logout from all sessions
fetch('https://door.casdoor.com/api/sso-logout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
console.log('Logout successful:', data);
window.location.href = '/login';
})
.catch(error => {
console.error('Logout failed:', error);
});

// Logout from current session only
fetch('https://door.casdoor.com/api/sso-logout?logoutAll=false', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
console.log('Logged out from current session:', data);
window.location.href = '/login';
});

With the Go SDK:

import "github.com/casdoor/casdoor-go-sdk/casdoorsdk"

func logout(w http.ResponseWriter, r *http.Request) {
// Get the access token from the session or request
token := getAccessTokenFromSession(r)

// Call the SSO logout endpoint
err := casdoorsdk.Logout(token)
if err != nil {
http.Error(w, "Logout failed", http.StatusInternalServerError)
return
}

// Clear local session
clearSession(w, r)

// Redirect to login page
http.Redirect(w, r, "/login", http.StatusFound)
}

With the JavaScript SDK:

import Sdk from "casdoor-js-sdk";

const CasdoorSDK = new Sdk({
serverUrl: "https://door.casdoor.com",
clientId: "YOUR_CLIENT_ID",
appName: "YOUR_APP_NAME",
organizationName: "YOUR_ORG_NAME",
});

async function handleLogout() {
try {
// Call the SSO logout endpoint
await CasdoorSDK.logout();

// Clear local state
localStorage.removeItem('casdoor_token');
sessionStorage.clear();

// Redirect to login page
window.location.href = '/login';
} catch (error) {
console.error('Logout failed:', error);
}
}

With the Python SDK:

from casdoor import CasdoorSDK

sdk = CasdoorSDK(
endpoint="https://door.casdoor.com",
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
certificate="YOUR_CERT",
org_name="YOUR_ORG_NAME",
app_name="YOUR_APP_NAME",
)

def logout(access_token):
try:
# Call the SSO logout endpoint
result = sdk.logout(access_token)

if result['status'] == 'ok':
# Clear local session
clear_session()
return True
else:
print(f"Logout failed: {result['msg']}")
return False
except Exception as e:
print(f"Logout error: {e}")
return False

Whatever the result of the request, clear the local state of your application afterward: the stored tokens, the cached user, and local session cookies. A user who chose to sign out must appear signed out even if the request to Casdoor failed.

async function logout() {
try {
await callSSOLogout();
} catch (error) {
console.error('SSO logout failed, clearing local state anyway:', error);
} finally {
// Always clear local state
clearLocalAuthenticationState();
redirectToLogin();
}
}

Receive sign-out notifications​

On sign-out, Casdoor sends a notification to each notification provider of the application that the user signed up with. Your applications use the notification to end their own sessions for that user.

Configure the notification provider​

  1. In the Casdoor admin console, add a notification provider, for example of type Custom HTTP, and add it to the application.

  2. For a Custom HTTP provider, set the following fields:

    FieldValue
    EndpointEndpoint of your application that receives the notification, for example https://app.example.com/api/logout-webhook
    MethodPOST
    Parametercontent. This is the name of the form field that carries the JSON payload

Read the notification​

Casdoor sends a POST request with the following payload:

{
"owner": "org-name",
"name": "username",
"displayName": "John Doe",
"email": "user@example.com",
"phone": "+1234567890",
"id": "user-id",
"event": "sso-logout",
"sessionIds": ["session-123", "session-456"],
"accessTokenHashes": ["hash-abc", "hash-def"],
"nonce": "random-nonce-xyz",
"timestamp": 1699900000,
"signature": "hmac-sha256-signature"
}
FieldDescription
sessionIdsIDs of the sessions that were ended
accessTokenHashesSHA-256 hashes of the access tokens that were expired. Compare them with the hashes of the tokens that your application holds to find the sessions to end
nonceRandom value that protects against replay
timestampUnix time at which Casdoor created the notification
signatureHMAC-SHA256 signature, computed with the client secret of the application

Verify the notification​

Anyone who can reach your endpoint can send a request to it. Before you act on a notification:

  1. Verify the signature. In Go:

    // Example verification in Go
    func verifyLogoutNotification(notification *SsoLogoutNotification, clientSecret string) bool {
    data := fmt.Sprintf("%s|%s|%s|%d|%s|%s",
    notification.Owner,
    notification.Name,
    notification.Nonce,
    notification.Timestamp,
    strings.Join(notification.SessionIds, ","),
    strings.Join(notification.AccessTokenHashes, ","))

    expectedSignature := hmacSHA256(clientSecret, data)
    return notification.Signature == expectedSignature
    }

    In JavaScript:

    // Example verification in JavaScript
    const crypto = require('crypto');

    function verifyLogoutNotification(notification, clientSecret) {
    const data = `${notification.owner}|${notification.name}|${notification.nonce}|${notification.timestamp}|${notification.sessionIds.join(',')}|${notification.accessTokenHashes.join(',')}`;

    const expectedSignature = crypto
    .createHmac('sha256', clientSecret)
    .update(data)
    .digest('hex');

    return notification.signature === expectedSignature;
    }
  2. Reject notifications that are too old, for example older than five minutes:

    function isNotificationValid(notification) {
    const maxAge = 5 * 60 * 1000; // 5 minutes in milliseconds
    const now = Date.now();
    const notificationTime = notification.timestamp * 1000; // Convert to milliseconds

    if (now - notificationTime > maxAge) {
    console.error('Notification is too old, possible replay attack');
    return false;
    }

    return verifyLogoutNotification(notification, clientSecret);
    }
  3. End the sessions in your application that match sessionIds or accessTokenHashes.

Vorsicht

The client secret signs the notifications. Keep it on the server, for example in an environment variable or a secret manager, and never ship it in client-side code.

Receive OIDC back-channel logout tokens​

Casdoor supports OpenID Connect Back-Channel Logout 1.0. When a user signs out through RP-initiated logout, Casdoor sends a signed logout token to every application that has an active session for the user. Each application then ends its own session, server to server.

  1. In the Casdoor admin console, open the edit page of the application.
  2. Set Backchannel logout URL (backchannelLogoutUri) to the endpoint of your application that receives the logout token.

Casdoor sends an HTTP POST request with a logout_token parameter to that URI. The token is a JSON Web Token (JWT) with the sub and sid claims and the http://schemas.openid.net/event/backchannel-logout event. Casdoor skips applications that have no back-channel logout URL.

Fehlerbehebung​

The user stays signed in to some applications​

  • Check that all applications sign users in through Casdoor and belong to the same organization.
  • Check that each application validates the token on every request, or acts on the sign-out notification. An application that relies only on its own local session doesn't notice the sign-out.

A token still works after sign-out​

  • Check that the request went to /api/sso-logout and was authenticated as the user.
  • Check that your applications don't cache the result of token validation.

After a full sign-out, the token records stay in the database with ExpiresIn set to 0. The token introspection endpoint and the refresh token endpoint reject them. A client that uses a refresh token after sign-out receives the error invalid_grant with the message "refresh token is invalid, expired or revoked".

The endpoint returns an error​

StatusCauseWhat to do
HTTP 401The access token is invalid or expiredClear the local state and send the user to the sign-in page
HTTP 403The user isn't allowed to sign out. This points to a configuration problemCheck the configuration of the application
HTTP 500Error on the serverLog the error and clear the local state anyway
async function logout() {
try {
// Replace with your actual Casdoor server URL
const response = await fetch('https://door.casdoor.com/api/sso-logout', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});

if (!response.ok) {
console.error(`Logout failed with status: ${response.status}`);
// Clear local state anyway
}
} catch (error) {
console.error('Logout error:', error);
} finally {
clearLocalAuthenticationState();
redirectToLogin();
}
}

See also​