Skip to content

Backchannel Logout#1573

Open
lullis wants to merge 2 commits intodjango-oauth:masterfrom
mushroomlabs:1545_backchannel_logout
Open

Backchannel Logout#1573
lullis wants to merge 2 commits intodjango-oauth:masterfrom
mushroomlabs:1545_backchannel_logout

Conversation

@lullis
Copy link
Copy Markdown
Contributor

@lullis lullis commented May 6, 2025

  • Add migration to add backchannel logout support
  • Add parameters related to backchannel logout on OIDC Discovery View
  • Change application creation and update form
  • Change template that renders information about the application
  • Add "handlers" module for signals handlers definition.

Fixes #1545

Description of the Change

Checklist

  • PR only contains one change (considered splitting up PR)
  • unit-test added
  • documentation updated
  • CHANGELOG.md updated (only for user relevant changes)
  • author name in AUTHORS

@lullis lullis requested a review from tonial May 6, 2025 10:59
@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented May 6, 2025

Before going further with this PR and working on tests/documentation, could please someone comment about the general structure of the implementation?

My main concerns:

  • This is implemented as a handler of the user_logged_out signal, which means that it will be executed during the logout cycle. I'm not sure if it's a good idea to have a potentially blocking operation as part of the request cycle. If it is not a good idea to have this being executed on the request cycle, then what would be the recommended approach? Should this functionality try to auto-detect background workers/celery/django-rq, or perhaps just provide this function in some form of "workers" module and leave it up to application developers to hook it up themselves?
  • I'm reasonably sure that I shouldn't be using request.session.session_key for the session identifier (it's not guaranteed that IDPs will be using it on their servers), but on the other hand I'm not entirely sure what should be used as the "sid" field for RPs that require it: should it be derived from the IDLoginToken somehow?
  • Logic being in the "models" module instead of "validators": unless I'm mistaken, oauth logic is delegated to validators and oauthlib to handle all forms of RP requests, but given that backchannel logout is something where the OP is initiating the request, it seems different from everything else so far.

@dopry
Copy link
Copy Markdown
Member

dopry commented May 19, 2025

@lullis

  1. for now the back channel can live in the logout request, we can move it later. Maybe start with a fire and forget approach to minimize blocking. Rather than logging out as a part of the OP logout, maybe we have a list of clients to explicitly logout without necessarily terminating the OP session?
  2. You should be using the sid from the tokens I believe. Do we issue the sid claim currently? If not this may not be applicable at this juncture.
  3. not sure off hand. will look more closely, but try to following existing patterns where possible.

@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented May 31, 2025

@dopry

  1. Maybe I am missing something, but how would you set up any fire-and-forget function handler in the response-request cycle?

  2. Yeah, I don't think there is anything about the sid claim. So would it be okay if we had a first version where the server supports backchannel logout, but not the session identifier?

@lullis lullis force-pushed the 1545_backchannel_logout branch 3 times, most recently from 36a433e to 4aeac81 Compare June 3, 2025 01:58
@codecov
Copy link
Copy Markdown

codecov bot commented Jun 3, 2025

Codecov Report

Attention: Patch coverage is 69.72477% with 33 lines in your changes missing coverage. Please review.

Project coverage is 96.06%. Comparing base (8d3e7a9) to head (4aeac81).

Files with missing lines Patch % Lines
oauth2_provider/models.py 54.68% 29 Missing ⚠️
oauth2_provider/views/oidc.py 33.33% 2 Missing ⚠️
oauth2_provider/checks.py 88.88% 1 Missing ⚠️
oauth2_provider/views/application.py 88.88% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1573      +/-   ##
==========================================
- Coverage   97.38%   96.06%   -1.32%     
==========================================
  Files          34       35       +1     
  Lines        2214     2315     +101     
==========================================
+ Hits         2156     2224      +68     
- Misses         58       91      +33     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lullis lullis force-pushed the 1545_backchannel_logout branch from 4aeac81 to 8750834 Compare June 3, 2025 03:10
@lullis lullis marked this pull request as ready for review June 3, 2025 10:08
@dopry
Copy link
Copy Markdown
Member

dopry commented Jun 3, 2025

  1. I forget the world hasn't moved to async/await across the board. It's fine to do the work in the signal handler.
  2. The spec says A Logout Token MUST contain either a sub or a sid Claim, and MAY contain both. If a sid Claim is not present, the intent is that all sessions at the RP for the End-User identified by the iss and sub Claims be logged out. so if there is no SID, we need a SUB.

@lullis lullis force-pushed the 1545_backchannel_logout branch from b89d426 to d3c2684 Compare June 6, 2025 12:54
@dopry
Copy link
Copy Markdown
Member

dopry commented Jun 10, 2025

@lullis it looks like the next step is expanding test coverage right now the patch is just shy of 70%, our target is 100% coverage on patches going forward. At the very least we aim to match the current total coverage.

@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented Jun 11, 2025

@dopry this coverage report seems to be stale and based on a previous commit. How can I re-run the coverage report?

@dopry
Copy link
Copy Markdown
Member

dopry commented Jun 13, 2025

It seems something has changed with the jazzband Codecov account and our codecov uploads are getting rate limited. I don't have the necessary permissions to add a the codecov repository upload token to the repo secrets and update our CI to use it. We're working on transferring out of jazzband right now to get away from those types of restrictions. In the meantime I'll keep trying to rerun the build to get coverage uploaded.

@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented Jun 13, 2025

I will take a look to see if I can run the report of the diff locally. Running coverage for the whole report showed that all files touched were at least at 99% coverage.

@dopry
Copy link
Copy Markdown
Member

dopry commented Jul 11, 2025

@lullis I hear you. Right now I'm in a bit of a holding pattern waiting on @jezdez to transfer the project out of jazzband so we can fix our codecov setup and get back to merging code. He was supposed to do it the last two weeks, but flaked each time.

@hansegucker
Copy link
Copy Markdown

Hi @dopry, is there any update?

@dopry
Copy link
Copy Markdown
Member

dopry commented Jul 30, 2025

No @jezdez still hasn't transferred the repo.

@dopry
Copy link
Copy Markdown
Member

dopry commented Aug 11, 2025

@lullis org is transferred. Can you rebase this PR on the latest master?

@lullis lullis force-pushed the 1545_backchannel_logout branch from 73f43ce to 4e37550 Compare August 11, 2025 19:31
@codecov
Copy link
Copy Markdown

codecov bot commented Aug 11, 2025

Codecov Report

❌ Patch coverage is 98.79518% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
oauth2_provider/handlers.py 98.18% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@dopry
Copy link
Copy Markdown
Member

dopry commented Aug 11, 2025

@lullis it looks like we still have some pretty big gaps in test code coverage for the backchannel logout functionality. Will you have time to work on that? Is there another of your PRs that you would want to prioritize?

@lullis lullis force-pushed the 1545_backchannel_logout branch 4 times, most recently from a58ff7f to 780a113 Compare August 12, 2025 09:26
@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented Aug 12, 2025

@dopry , I've increased the test coverage as much as I could, but codecov is hung up on one line on application/models.py not being covered. Any tips on the best approach to test that exceptions are being caught in the middle of a function, or is it okay as it is?

@lullis lullis force-pushed the 1545_backchannel_logout branch from 780a113 to 81d83ec Compare August 12, 2025 09:34
@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented Dec 18, 2025

@dopry please take a look again. I did not squash this commit because in case I did not understand your description of the problem correct,

As an aside, can you check why the tests would be failing only for the specific combination of python 3.14 and django 5.2? I could not reproduce the issue locally.

@thisislawatts
Copy link
Copy Markdown

Thanks @lullis for all the time and energy getting the PR to the current state. I am sketching out a solution for my local Makerspace and this functionality would be a great option!

I believe the test failure maybe tied to: #1631

@lullis lullis force-pushed the 1545_backchannel_logout branch from 95c5ce9 to 1812980 Compare January 8, 2026 21:42
@dopry
Copy link
Copy Markdown
Member

dopry commented Jan 9, 2026

@lullis this looks good. Are there going to be cases where a user has an access token that is not tied to a refresh token? Do we need to query access tokens to find any of those? I wouldn't want to miss a session that didn't have a refresh token, but still needed a notification, but I'm not sure if that happens in practice offhand.

@lullis lullis force-pushed the 1545_backchannel_logout branch from 1812980 to acc64af Compare January 12, 2026 16:10
@Natureshadow
Copy link
Copy Markdown
Contributor

@dopry Does @lullis ' change in e229dac address your requested changes?

@dopry
Copy link
Copy Markdown
Member

dopry commented Mar 12, 2026

I believe so. This needs to be rebased so I can test. Unfortunately, the mushroomlabs repo is restricted such that I cannot rebase it or make any minor fixes that might be blocking merging...

@dopry dopry requested review from Copilot and removed request for tonial March 13, 2026 02:54
Copy link
Copy Markdown
Contributor

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

Adds OpenID Connect Backchannel Logout support to django-oauth-toolkit by introducing an Application-level backchannel logout URI, advertising support via OIDC discovery metadata, and wiring logout signal handling to send logout tokens to RPs.

Changes:

  • Add backchannel_logout_uri to Application (model + migration) and expose it in the application create/update UI.
  • Add OIDC discovery metadata fields for backchannel logout when enabled.
  • Add signal handler + checks + documentation and tests for backchannel logout behavior.

Reviewed changes

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

Show a summary per file
File Description
oauth2_provider/handlers.py Implements logout token creation and signal-driven dispatch to RP backchannel logout endpoints.
oauth2_provider/checks.py Adds Django system checks for backchannel logout configuration.
oauth2_provider/exceptions.py Adds a dedicated exception type for backchannel logout request failures.
oauth2_provider/models.py Adds backchannel_logout_uri field on Application.
oauth2_provider/migrations/0015_application_backchannel_logout_uri.py Migrates DB schema to include the new Application field.
oauth2_provider/views/oidc.py Publishes backchannel logout capability in OIDC discovery when enabled.
oauth2_provider/views/application.py Adds conditional form field for editing backchannel_logout_uri based on settings.
oauth2_provider/templates/oauth2_provider/application_detail.html Displays the configured backchannel logout URI on the application detail page.
oauth2_provider/settings.py Introduces backchannel logout settings and import-string handling.
oauth2_provider/apps.py Ensures handlers are imported so signal receivers are registered.
tests/test_backchannel_logout.py Adds tests for handler invocation, request sending, and application form integration.
tests/test_django_checks.py Adds checks tests for invalid handler / missing issuer endpoint.
tests/test_oidc_views.py Adds discovery metadata tests for backchannel logout flags.
tests/presets.py Adds OIDC settings presets for backchannel logout test configuration.
docs/settings.rst Documents new settings for backchannel logout.
docs/oidc.rst Documents how to enable/use backchannel logout.
CHANGELOG.md Notes addition of backchannel logout support.
AUTHORS Adds contributor.

@lullis lullis force-pushed the 1545_backchannel_logout branch from e229dac to 3c05cbe Compare March 21, 2026 07:21
@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented Mar 21, 2026

@dopry , rebased again and implemented the suggested changes given by automated review

Copy link
Copy Markdown
Contributor

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

Adds OpenID Connect Back-Channel Logout support to django-oauth-toolkit by introducing a per-Application backchannel logout URI, advertising capability via OIDC discovery, and wiring logout signal handling to send logout tokens.

Changes:

  • Add backchannel_logout_uri to Applications (model + migration) and expose it in application create/update UI and detail template.
  • Add backchannel logout metadata to OIDC discovery when enabled, plus Django system checks and new handler module for signal-driven logout token delivery.
  • Add unit tests and documentation/changelog updates for the new feature.

Reviewed changes

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

Show a summary per file
File Description
tests/test_oidc_views.py Tests discovery document advertising backchannel logout support.
tests/test_django_checks.py Tests new Django system checks for backchannel logout configuration.
tests/test_backchannel_logout.py New end-to-end/unit tests for signal handling and logout token sending.
tests/presets.py Adds an OIDC settings preset enabling backchannel logout for tests.
oauth2_provider/views/oidc.py Adds discovery metadata fields for backchannel logout when enabled.
oauth2_provider/views/application.py Conditionally adds backchannel_logout_uri to application editor forms.
oauth2_provider/templates/oauth2_provider/application_detail.html Displays backchannel_logout_uri in the application detail page.
oauth2_provider/settings.py Introduces OIDC_BACKCHANNEL_LOGOUT_* settings (default + import-string support).
oauth2_provider/models.py Adds backchannel_logout_uri field to the Application model.
oauth2_provider/migrations/0015_application_backchannel_logout_uri.py Migration adding the new Application field.
oauth2_provider/handlers.py New signal receiver + default logout request sender implementation.
oauth2_provider/exceptions.py Adds BackchannelLogoutRequestError.
oauth2_provider/checks.py Adds system check validation for backchannel logout configuration.
oauth2_provider/apps.py Ensures handlers module is imported so signal receivers register.
docs/settings.rst Documents new settings.
docs/oidc.rst Documents backchannel logout feature usage.
CHANGELOG.md Notes feature addition in unreleased changelog.
AUTHORS Adds contributor.

@lullis lullis force-pushed the 1545_backchannel_logout branch 3 times, most recently from e8e256e to 449540d Compare March 31, 2026 15:34
@dopry dopry requested a review from Copilot April 1, 2026 01:05
Copy link
Copy Markdown
Contributor

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

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

Comment on lines +18 to +21
logger = logging.getLogger(__name__)

BACKCHANNEL_LOGOUT_TIMEOUT = getattr(oauth2_settings, "OIDC_BACKCHANNEL_LOGOUT_TIMEOUT", 5)

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

BACKCHANNEL_LOGOUT_TIMEOUT is read via getattr(oauth2_settings, "OIDC_BACKCHANNEL_LOGOUT_TIMEOUT", 5), but OIDC_BACKCHANNEL_LOGOUT_TIMEOUT is not defined in oauth2_provider.settings.DEFAULTS. Because oauth2_settings.__getattr__ rejects unknown keys, any user-supplied OAUTH2_PROVIDER["OIDC_BACKCHANNEL_LOGOUT_TIMEOUT"] will be ignored and this will always fall back to 5. Add OIDC_BACKCHANNEL_LOGOUT_TIMEOUT to DEFAULTS (and docs if intended as public), and consider reading it inside send_backchannel_logout_request() instead of at import time so overrides/tests take effect.

Copilot uses AI. Check for mistakes.
)
response.raise_for_status()
except requests.RequestException as exc:
raise BackchannelLogoutRequestError(str(exc))
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

send_backchannel_logout_request re-raises BackchannelLogoutRequestError(str(exc)) without exception chaining, which discards the original traceback and makes debugging/observability harder. Re-raise using raise ... from exc so the original request failure context is preserved.

Suggested change
raise BackchannelLogoutRequestError(str(exc))
raise BackchannelLogoutRequestError(str(exc)) from exc

Copilot uses AI. Check for mistakes.
default="",
)
backchannel_logout_uri = models.URLField(
blank=True, null=True, help_text=_("Backchannel Logout URI where logout tokens will be sent")
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

backchannel_logout_uri is stored as a plain URLField but isn’t validated consistently with other URI-like Application fields (e.g., redirect_uris / allowed_origins use AllowedURIValidator + ALLOWED_*SCHEMES in AbstractApplication.clean). Since this value is later used for server-side requests.post, it should be constrained to supported schemes (at least http/https) and validated similarly to avoid storing unsupported/unsafe URIs.

Suggested change
blank=True, null=True, help_text=_("Backchannel Logout URI where logout tokens will be sent")
blank=True,
null=True,
help_text=_("Backchannel Logout URI where logout tokens will be sent"),
validators=[AllowedURIValidator(oauth2_settings.ALLOWED_REDIRECT_URI_SCHEMES)],

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should validate to the spec, but not with oauth2_settings.ALLOWED_REDIRECT_URI_SCHEMES as they're for redirect uris. Also keep in mind that the spec says

   backchannel_logout_uri
      OPTIONAL.  RP URL that will cause the RP to log itself out when
      sent a Logout Token by the OP.  This URL SHOULD use the "https"
      scheme and MAY contain port, path, and query parameter components;
      however, it MAY use the "http" scheme, provided that the Client
      Type is "confidential", as defined in Section 2.1 of OAuth 2.0
      [RFC6749], and provided the OP allows the use of "http" RP URIs.

and we are permissive with http for non-confidential clients on localhost for development and testing scenarios where certs aren't always easy to use. see: docs/settings.rst ALLOWED_SCHEMES for some examples of the language we've used when allowing this in the past and the implementations for how we warn and communicate about these risks at run time.


<li>
<p><b>{% trans "Backchannel Logout URI" %}</b></p>
<input class="input-block-level" type="text" value="{{ application.backchannel_logout_uri }}" readonly>
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

When application.backchannel_logout_uri is None, this renders as the literal string "None" in the input’s value attribute. Use |default_if_none:"" (and/or conditional rendering) so the field displays as empty when unset.

Suggested change
<input class="input-block-level" type="text" value="{{ application.backchannel_logout_uri }}" readonly>
<input class="input-block-level" type="text" value="{{ application.backchannel_logout_uri|default_if_none:"" }}" readonly>

Copilot uses AI. Check for mistakes.
@dopry
Copy link
Copy Markdown
Member

dopry commented Apr 1, 2026

@lullis thank you for attending to the requests of our pedenatic overlords. There is another round. I wish I could push to your branch here becuase I'd be happy to address these and put a bow on it... unfortunately I don't seem to have permissions to do that with org forks as I do with individual contributors.

@lullis
Copy link
Copy Markdown
Contributor Author

lullis commented Apr 1, 2026

Hi @dopry. I understand it might help you to deal with some of the maintenance load, but to be 100% honest dealing with Copilot is quite demotivating, At least for me, it makes me less interested in becoming a more active contributor to the project.

Anyway, I gave you write permission to my repo. Let me know if this helps us make quicker progress.

@dopry
Copy link
Copy Markdown
Member

dopry commented Apr 1, 2026

@lullis thanks for the permissions. I've confirmed they're working with a rebase. I'll support where I can and notify you when I make/push changes.

I hear you regarding copilot. I'd love to hear more about what you find demotivating. I started a "Copilot, thoughts?" discussion topic for the conversation.

lullis added 2 commits April 1, 2026 10:15
 - Add migration to add backchannel logout support
 - Add parameters related to backchannel logout on OIDC Discovery View
 - Change application creation and update form
 - Change template that renders information about the application
@dopry dopry force-pushed the 1545_backchannel_logout branch from 449540d to ce5bde7 Compare April 1, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OIDC Back-Channel Logout

6 participants