Skip to content

Conversation

qbey
Copy link
Member

@qbey qbey commented Oct 7, 2025

Purpose

This endpoint can be called by the IdP to kill all the user sessions.

In order to allow to kill only one session, we need to store the session ID when login from IdP...

Proposal

Description...

  • add backchannel logout endpoint and logic
  • add the IdP session ID storage when login from IdP

Summary by CodeRabbit

  • New Features

    • Added an OIDC back-channel logout endpoint to support IdP-initiated session termination.
    • Session now records the OIDC session ID (sid) to enable targeted logout.
    • Enhanced security with strict token validation and replay protection for logout requests.
  • Tests

    • Added comprehensive test coverage for back-channel logout flows, including success, error handling, replay prevention, and end-to-end verification.
  • Style

    • Minor formatting cleanup in repository imports.

@qbey qbey self-assigned this Oct 7, 2025
@qbey qbey added the enhancement New feature or request label Oct 7, 2025
@qbey qbey force-pushed the qbey/add-backchannel-logout branch 2 times, most recently from 079d690 to 9e2683b Compare October 7, 2025 18:43
@qbey qbey requested a review from Copilot October 7, 2025 19:05
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements OIDC back-channel logout functionality according to the OpenID Connect Back-Channel Logout 1.0 specification. The IdP can now send server-to-server logout requests to kill user sessions when they log out elsewhere.

  • Adds a new OIDCBackChannelLogoutView that handles JWT-based logout requests from identity providers
  • Stores the OIDC session ID (sid) during authentication for future session targeting
  • Includes comprehensive JWT validation, JTI replay protection, and session management

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/lasuite/oidc_login/views.py Adds the main OIDCBackChannelLogoutView class with complete OIDC logout implementation
src/lasuite/oidc_login/urls.py Adds URL routing for the new backchannel logout endpoint
src/lasuite/oidc_login/backends.py Stores OIDC session ID (sid) in session during authentication
tests/oidc_login/test_views.py Comprehensive test coverage for all backchannel logout scenarios
gitlint/gitlint_emoji.py Removes extra blank line (formatting cleanup)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@qbey qbey force-pushed the qbey/add-backchannel-logout branch from e0220ad to 8c9966d Compare October 7, 2025 19:09
Copy link

coderabbitai bot commented Oct 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds an OpenID Connect back-channel logout endpoint and URL; implements logout_token JWT validation, JTI replay protection, session termination; stores token "sid" in session during authenticate; includes extensive back-channel logout tests; minor formatting tweak in a gitlint file.

Changes

Cohort / File(s) Summary of edits
OIDC back-channel logout view
src/lasuite/oidc_login/views.py
Adds OIDCBackChannelLogoutView (CSRF-exempt) to accept POSTed logout_token, validate JWT signature/claims (typ, iss, aud, exp/iat, jti, events, no nonce), enforce JTI replay protection via cache, lookup users/sessions by sub/sid, terminate sessions, and return no-store responses or JSON error payloads. Adds User = get_user_model() and logger.
URL routing
src/lasuite/oidc_login/urls.py
Imports OIDCBackChannelLogoutView and registers new route backchannel-logout/ named oidc_backchannel_logout.
Authenticate session storage
src/lasuite/oidc_login/backends.py
On successful authenticate, stores token sid into session under key oidc_sid in addition to existing refresh token storage.
Tests: back-channel logout
tests/oidc_login/test_views.py
Adds unit/integration tests covering missing/invalid logout_token, missing sub/sid, JTI replay prevention, successful logout (204) and failure cases, JWKS-based validation, JTI caching, and session handling across users/SIDs.
Formatting tweak
gitlint/gitlint_emoji.py
Removes a blank line between imports; no behavioral change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor IdP as OIDC Provider
  participant App as App Server
  participant BCL as Back-Channel Logout View
  participant JWT as JWT Validator (JOSE)
  participant Cache as JTI Cache
  participant Sess as Session Store
  participant DB as User DB

  rect rgb(245,245,255)
    note over IdP,App: Back-channel logout flow
    IdP->>BCL: POST /backchannel-logout (logout_token)
    BCL->>JWT: Validate signature + claims (typ, iss, aud, exp/iat, jti, events, no nonce)
    alt invalid token/claims
      JWT-->>BCL: Error
      BCL-->>IdP: 400 JSON (Cache-Control: no-store)
    else valid
      BCL->>Cache: Check/add jti (replay protection)
      alt replay detected
        Cache-->>BCL: Exists
        BCL-->>IdP: 400 JSON (replay)
      else first-seen
        Cache-->>BCL: Stored
        opt Resolve sessions
          alt sid present
            BCL->>Sess: Terminate session(s) by sid
          else sub present
            BCL->>DB: Lookup user(s) by sub
            BCL->>Sess: Terminate all sessions for user(s)
          end
        end
        BCL-->>IdP: 204 No Content (Cache-Control: no-store)
      end
    end
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant App as App Server
  participant Auth as OIDC Backend
  participant Sess as Session

  note over User,App: Authentication flow (sid stored)
  User->>App: Login via OIDC
  App->>Auth: authenticate(token_info)
  Auth->>Sess: Save refresh token
  Auth->>Sess: Save `oidc_sid` = token_info["sid"]
  Auth-->>App: Return User object
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Thump-thump, I guard the logout gate,
A sid tucked safe in session state.
JTI nets the echoed knock,
Sessions quiet — no ticking clock.
Rabbit hops — back-channel calm and great. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the main change by indicating the addition of an OIDC logout endpoint and uses concise phrasing to convey the PR’s primary purpose.
Docstring Coverage ✅ Passed Docstring coverage is 94.59% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch qbey/add-backchannel-logout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qbey
Copy link
Member Author

qbey commented Oct 7, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Oct 7, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c771bae and 8c9966d.

📒 Files selected for processing (5)
  • gitlint/gitlint_emoji.py (0 hunks)
  • src/lasuite/oidc_login/backends.py (1 hunks)
  • src/lasuite/oidc_login/urls.py (2 hunks)
  • src/lasuite/oidc_login/views.py (3 hunks)
  • tests/oidc_login/test_views.py (3 hunks)
💤 Files with no reviewable changes (1)
  • gitlint/gitlint_emoji.py

@qbey
Copy link
Member Author

qbey commented Oct 8, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Oct 8, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

if success:
logger.info("Back-channel logout successful for sub=%s, sid=%s", sub, sid)
# According to the spec, return 200 OK
response = HttpResponse(status=200)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
response = HttpResponse(status=200)
response = HttpResponse(status=204)

Without body, a 204 should be used (coucou coderabbit)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the logout succeeded, the RP MUST respond with HTTP 200 OK. However, note that some Web frameworks will substitute an HTTP 204 No Content response for an HTTP 200 OK when the HTTP body is empty. Therefore, OPs should be prepared to also process an HTTP 204 No Content response as a successful response.

Indeed 👍

qbey added 2 commits October 8, 2025 13:47
This endpoint can be called by the IdP to kill all the user
sessions.

In order to allow to kill only one session, we need to store the
session ID when login from IdP...
Storing this ID allows to kill only a specific session
when calling the backchannel logout endpoint.
@qbey qbey force-pushed the qbey/add-backchannel-logout branch from ee22b23 to 4e27243 Compare October 8, 2025 11:47
@qbey qbey marked this pull request as ready for review October 8, 2025 11:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/lasuite/oidc_login/views.py (1)

575-607: Add explicit pass statements to empty method bodies.

The placeholder methods revoke_refresh_tokens and propagate_logout_to_downstream_rps should include pass statements or raise NotImplementedError to make their unimplemented status explicit.

Apply this diff:

     def revoke_refresh_tokens(self, user):
         """
         Revoke user's refresh tokens (section 2.7 of the spec).
 
         According to the spec:
         - Refresh tokens WITHOUT offline_access MUST be revoked
         - Refresh tokens WITH offline_access MUST NOT be revoked normally
 
         NOTE: This implementation depends on the token management system.
         To be implemented if needed by the project
 
         Args:
             user: The user whose tokens should be revoked
 
         """
+        pass
 
     def propagate_logout_to_downstream_rps(self, user, sub, sid):
         """
         Propagate logout to downstream RPs if this RP is also an OP.
 
         According to section 2.7 of the spec, if the RP receiving the logout
         is itself an OP serving other RPs, it should propagate
         the logout by sending logout requests to its own RPs.
 
         NOTE: To be implemented if needed by the project.
 
         Args:
             user: The user to log out
             sub: Subject identifier
             sid: Session ID (optional)
 
         """
+        pass
🧹 Nitpick comments (1)
src/lasuite/oidc_login/views.py (1)

486-508: Session iteration may impact performance at scale.

The code iterates through all active sessions (lines 486, 513) to find matching user sessions. With thousands of concurrent sessions, this could become slow. However, Django's session framework doesn't provide efficient querying by decoded session data, so this approach is reasonable given the framework constraints.

For production deployments with high session counts, consider:

  • Using a fast cache backend (Redis/Memcached) for session storage
  • Implementing session partitioning or custom session storage that allows querying by user_id/oidc_sid
  • Monitoring backchannel logout endpoint performance and setting appropriate timeouts

Also applies to: 513-550

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 608f04b and 4e27243.

📒 Files selected for processing (5)
  • gitlint/gitlint_emoji.py (0 hunks)
  • src/lasuite/oidc_login/backends.py (1 hunks)
  • src/lasuite/oidc_login/urls.py (2 hunks)
  • src/lasuite/oidc_login/views.py (3 hunks)
  • tests/oidc_login/test_views.py (3 hunks)
💤 Files with no reviewable changes (1)
  • gitlint/gitlint_emoji.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lasuite/oidc_login/backends.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/lasuite/oidc_login/urls.py (1)
src/lasuite/oidc_login/views.py (1)
  • OIDCBackChannelLogoutView (163-606)
src/lasuite/oidc_login/views.py (2)
src/lasuite/oidc_login/backends.py (1)
  • OIDCAuthenticationBackend (58-312)
tests/test_project/user/models.py (1)
  • User (7-18)
tests/oidc_login/test_views.py (2)
src/lasuite/oidc_login/views.py (7)
  • OIDCBackChannelLogoutView (163-606)
  • post (102-122)
  • post (195-249)
  • error_response (251-269)
  • is_valid_logout_event (379-411)
  • check_and_store_jti (413-447)
  • logout_user_sessions (449-573)
tests/factories.py (1)
  • UserFactory (11-19)
🔇 Additional comments (6)
src/lasuite/oidc_login/urls.py (1)

6-6: LGTM! Clean URL wiring for the backchannel logout endpoint.

The import and URL pattern follow Django conventions and integrate well with the existing OIDC URL structure.

Also applies to: 16-20

tests/oidc_login/test_views.py (3)

365-489: Excellent test coverage for backchannel logout error paths.

The tests thoroughly cover validation failures (missing token, invalid token, missing claims, JTI replay) and verify correct error responses with appropriate status codes and headers.


569-648: Strong end-to-end test with real JWT validation.

The test generates a real RSA keypair, mocks the JWKS endpoint, and validates the full backchannel logout flow including JWT signature verification and session deletion. This provides good confidence in the implementation.


651-863: Comprehensive edge case coverage for session handling.

The tests cover important edge cases including:

  • sid-only logout with user resolution
  • Orphaned sessions (user deleted)
  • Anonymous sessions
  • Invalid user IDs
  • Multiple users/sessions

This thorough coverage helps prevent regressions.

src/lasuite/oidc_login/views.py (2)

470-573: Well-implemented fix for sid-only backchannel logout.

The refactored logic properly handles the sid-only case (lines 510-551) by:

  1. Searching for the session by sid first
  2. Lazily resolving the user from the session when found
  3. Deleting orphaned/anonymous sessions appropriately
  4. Only calling user-related methods when a user was resolved (lines 569-571)

This addresses the critical security issue flagged in the previous review where sid-only tokens were not properly logging out sessions.


287-340: Standardize on joserfc for JWT handling. The code currently uses PyJWT only for header inspection and byte conversion, while token decode and validation rely on joserfc.jwt. To avoid maintenance overhead and potential mismatches, replace pyjwt imports (get_unverified_header, DecodeError, InvalidTokenError, force_bytes) with the corresponding joserfc.jwt API once you confirm it supports unverified header extraction and equivalent error types.

@lunika lunika mentioned this pull request Oct 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants