Skip to content

OIDC Back-Channel Logout — Server-to-Server Session Termination

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about OIDC Back. We cover key concepts, practical examples, and best practices to help you master this topic.

Back-channel logout in Openid Connect sends a direct HTTP POST request from the provider to each application's logout endpoint when a session ends, terminating sessions server-to-server without requiring browser iframes.

What You'll Learn

  • How back-channel logout differs from front-channel logout
  • How to validate the logout token sent by the provider
  • When to use back-channel vs front-channel logout

Why It Matters

Front-channel logout requires a browser to load iframes. Mobile apps, background services, and server-side applications cannot participate. Back-channel logout notifies these applications directly via server-to-server HTTP requests, ensuring complete logout coverage.

Real-World Use

DodaTech's backend notification service maintains its own session with the OIDC provider. When the user logs out, the provider sends a back-channel logout request to the notification service, which immediately stops sending notifications and revokes the service's access token.

sequenceDiagram
    participant User
    participant Provider as OIDC Provider
    participant WebApp as DodaMail
    participant Service as Notification Service

    User->>Provider: Logout
    Provider->>WebApp: Front-Channel (iframe)
    Note over Provider,Service: Also send back-channel
    Provider->>Service: POST /backchannel_logout
    Note over Service: Validate logout_token
    Service-->>Provider: 200 OK
    Note over Service: Revoke tokens, stop processing

Implementing Back-Channel Logout Endpoint

from flask import Flask, request, jsonify
import jwt
import requests

app = Flask(__name__)

BACKCHANNEL_LOGOUT_ENDPOINT = "/backchannel_logout"
PROVIDER_ISSUER = "https://accounts.example.com"
CLIENT_ID = "doda-notification-service"

@app.route(BACKCHANNEL_LOGOUT_ENDPOINT, methods=['POST'])
def backchannel_logout():
    logout_token = request.form.get('logout_token')
    if not logout_token:
        return jsonify({"error": "missing_logout_token"}), 400

    # Fetch provider's JWKS for token validation
    jwks_uri = f"{PROVIDER_ISSUER}/.well-known/jwks.json"
    jwks = requests.get(jwks_uri).json()

    try:
        header = jwt.get_unverified_header(logout_token)
        key = next(k for k in jwks["keys"] if k["kid"] == header["kid"])

        payload = jwt.decode(
            logout_token,
            key,
            algorithms=["RS256"],
            issuer=PROVIDER_ISSUER,
            audience=CLIENT_ID
        )
    except jwt.InvalidTokenError as e:
        app.logger.error(f"Invalid logout token: {e}")
        return jsonify({"error": "invalid_token"}), 400

    # Extract user and session info
    subject = payload.get('sub')
    session_id = payload.get('sid')
    events = payload.get('events', {})

    # Verify this is a logout token
    if 'http://schemas.openid.net/event/backchannel-logout' not in events:
        app.logger.error("Not a logout token")
        return jsonify({"error": "not_logout_token"}), 400

    # Terminate the session
    terminate_session(subject, session_id)
    app.logger.info(f"Logout processed for user {subject}")

    return jsonify({"status": "ok"}), 200

Logout Token Validation

def validate_logout_token(logout_token, jwks_uri, expected_issuer, expected_audience):
    """Validate a back-channel logout token"""
    jwks = requests.get(jwks_uri).json()

    try:
        header = jwt.get_unverified_header(logout_token)
        matching_keys = [k for k in jwks["keys"] if k["kid"] == header.get("kid")]
        if not matching_keys:
            raise ValueError("No matching key found")

        payload = jwt.decode(
            logout_token,
            matching_keys[0],
            algorithms=["RS256"],
            issuer=expected_issuer,
            audience=expected_audience,
            options={"require": ["sub", "sid", "events", "iat"]}
        )

        # Verify events claim
        events = payload.get('events', {})
        logout_event = 'http://schemas.openid.net/event/backchannel-logout'
        if logout_event not in events:
            raise ValueError("Missing backchannel-logout event")

        # Verify token hasn't been used (replay prevention)
        if payload.get('jti') in USED_TOKEN_IDS:
            raise ValueError("Token has already been used")
        USED_TOKEN_IDS.add(payload.get('jti'))

        return payload

    except Exception as e:
        app.logger.error(f"Logout token validation failed: {e}")
        raise

Session Termination

def terminate_session(subject, session_id):
    """Terminate all session data for the given user"""
    # Revoke refresh tokens
    conn = get_redis_connection()
    refresh_token_key = f"refresh_token:{subject}:{session_id}"
    stored_token = conn.get(refresh_token_key)
    if stored_token:
        conn.delete(refresh_token_key)

    # Invalidate session cache
    session_key = f"session:{subject}:{session_id}"
    conn.delete(session_key)

    # Log termination
    app.logger.info(f"Session terminated: user={subject}, session={session_id}")

Common Mistakes

1. Not Validating the Logout Event Type

The logout token has a specific events claim. Without validating it, non-logout tokens could trigger session termination.

2. Missing Replay Prevention

The logout token contains a jti (JWT ID) claim. Track used jti values to prevent replay attacks.

3. Forgetting to Verify the Audience

The aud claim must match your application's client_id. Otherwise, a logout token intended for another app could terminate your sessions.

4. Returning Non-200 Responses

The provider expects a 200 OK response. Returning an error may cause the provider to retry or consider the logout incomplete.

5. Not Handling Idempotency

The provider may send the same logout token multiple times (network retries). Handle this with idempotency based on the jti claim.

Practice Questions

  1. How does back-channel logout differ from front-channel logout?
  2. What claims are required in a logout token?
  3. How do you prevent logout token replay attacks?
  4. Why might you need both front-channel and back-channel logout?
  5. What should your endpoint return on successful logout?

Answers

  1. Back-channel is server-to-server; front-channel uses browser iframes. 2. sub, sid, events, iat, jti. 3. Track and validate the jti (JWT ID) claim. 4. Front-channel covers browser-based apps; back-channel covers server-side services and mobile apps. 5. HTTP 200 OK with a JSON body.

Challenge

Build a back-channel logout receiver that handles logout tokens from multiple providers, validates each token, terminates the correct sessions, and returns appropriate error codes for invalid tokens with detailed logging.

FAQ

What is back-channel logout in OIDC?

A server-to-server logout mechanism where the provider sends an HTTP POST with a logout token to each application.

How does back-channel logout differ from front-channel?

Back-channel is server-to-server without browser involvement; front-channel uses browser iframes.

What is a logout token?

A JWT containing claims like sub, sid, events, and jti that the provider sends to notify applications of logout.

Why is replay prevention important for logout tokens?

An attacker could replay a logout token to terminate active sessions. The jti claim prevents this.

Can I use only back-channel logout?

Yes, if all your applications have server-side endpoints that can receive the HTTP POST.

Mini Project

Create a complete logout system with both front-channel and back-channel: a provider simulation that sends both types of logout, a server-side service that processes back-channel logout tokens, a web app that handles front-channel iframes, and a monitoring dashboard showing all received logouts.

What's Next

  • Learn about post-logout redirect URIs for the user experience after logout
  • Explore claims requests for fine-grained attribute control
  • Continue to distributed and aggregated claims for multi-source identity data

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro