Skip to content

feat(oauth): add branded oath consent page template#7340

Closed
nadahlberg wants to merge 7 commits into
mainfrom
add-oauth-consent-template
Closed

feat(oauth): add branded oath consent page template#7340
nadahlberg wants to merge 7 commits into
mainfrom
add-oauth-consent-template

Conversation

@nadahlberg

Copy link
Copy Markdown
Member

Fixes

Fixes #7228

Summary

This PR adds a template to render the /authorize route for the Django OAuth toolkit page. This is the page where user's consent when registering a new OAuth application.

Deployment

This PR should:

  • skip-deploy (skips everything below)
    • skip-web-deploy
    • skip-celery-deploy
    • skip-cronjob-deploy
    • skip-daemon-deploy

Screenshots

Desktop

Screenshot 2026-05-11 at 8 44 58 PM

Mobile

Screenshot 2026-05-11 at 8 45 05 PM

Comment thread cl/assets/templates/oauth2_provider/authorize.html Outdated
@mlissner

Copy link
Copy Markdown
Member

@claude review once

@mlissner

Copy link
Copy Markdown
Member

The "revoke access" link sort of answers a question I've been wondering: "How do I yank Claude's access to my API?" According to your screenshot above, the following URL should make that possible:

https://www.courtlistener.com/profile/api/

But it doesn't? Do we need another issue for this or is this something I'm not understanding?

Comment on lines +41 to +46
<div class="alert alert-warning v-offset-above-2" role="alert">
<i class="fa fa-exclamation-triangle"></i>
Only authorize applications you trust. They will be able to
act on your behalf within the permissions listed above until
you <a href="{% url "view_api" %}">revoke access</a>.
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 "Revoke access" link on the consent screen points to {% url "view_api" %} (/profile/api/), which only renders the API Documentation/Token/Usage developer tabs and has no UI for listing or revoking authorized OAuth applications — users following the link to revoke an app will land on an unrelated page. Consider pointing to django-oauth-toolkit's {% url "oauth2_provider:authorized-token-list" %} (already routed under /o/ via cl/oauth/urls.py) or a custom revocation page.

Extended reasoning...

What the bug is

On the OAuth consent screen, the warning alert tells users:

Only authorize applications you trust. They will be able to act on your behalf within the permissions listed above until you revoke access.

The revoke access link is wired to {% url "view_api" %}. That named route resolves to /profile/api/ (see cl/users/urls.py:119) and is served by view_api, which renders cl/users/templates/profile/api.html. That template only displays the developer-tools tab set (api_info, api_token, api_usage, api_webhooks, via cl/users/templates/includes/developer-tabs.html). There is no UI on any of those tabs for listing the user's authorized OAuth applications or revoking a previously granted authorization.

Why existing code doesn't prevent it

Nothing currently links the API profile page to OAuth authorization management — that page predates the OAuth integration and is purely about REST API tokens/usage. The consent template is a new file in this PR, so this is the first place that needed to know about an OAuth revocation URL, and it picked the wrong one.

Step-by-step proof

  1. A user grants an application access via this consent screen and is redirected back to the third-party app.
  2. Later the user wants to revoke that grant and remembers the consent screen said they could "revoke access" at the linked URL, so they revisit the page that contained the link (or follow it from a cached copy).
  3. The link reads <a href="{% url "view_api" %}">revoke access</a>. Django reverses view_api/profile/api/.
  4. The user lands on /profile/api/, which (per profile/api.html lines 15–20) shows a "Developer Tools" heading, a description of the REST API, and a link to API documentation. The sibling tabs (api_token, api_usage, api_webhooks) similarly have no OAuth-app management.
  5. The user has no way to revoke the authorization from any tab reachable through that link, despite the consent screen having promised they could.

Impact

This is on a security-sensitive page: the consent screen explicitly tells the user the authorization is reversible and points them at the (claimed) revocation control. Sending them to an unrelated developer-tools page undermines the safety message and could leave users unable to find the real revocation flow. Severity is functional/UX, but on a trust-relevant surface.

Suggested fix

django-oauth-toolkit already exposes an authorized-token list view via the base_urlpatterns that cl/oauth/urls.py:36 includes under /o/. The simplest fix is to point the link at that named URL:

-      you <a href="{% url "view_api" %}">revoke access</a>.
+      you <a href="{% url "oauth2_provider:authorized-token-list" %}">revoke access</a>.

Alternatively, swap in a custom revocation page if one is planned. Either way the current target should not ship as-is.

Comment thread cl/assets/templates/oauth2_provider/authorize.html Outdated
Comment thread cl/assets/templates/oauth2_provider/authorize.html
@nadahlberg

Copy link
Copy Markdown
Member Author

@mlissner no, you're right, we need a way for users to revoke apps. Maybe a little out of scope for this PR, but can we squeeze it in?

I added another tab to the user profile developer settings:

With an app:

Screenshot 2026-05-12 at 7 47 48 AM

Success on revokation state:

Screenshot 2026-05-12 at 7 48 04 AM

Mobile:

Screenshot 2026-05-12 at 7 47 39 AM

Also fixed the other two smaller issues Claude raised.

@mlissner

Copy link
Copy Markdown
Member

We have a bunch of user experiences around deletion in our profiles:

  • Alerts
  • Webhooks
  • This
  • Notes?
  • Visualizations?

If we're going to add this, we should at least make it follow one of these, but I'm not sure which and don't have time today to figure this out. If you want to take a look and try to suggest one, that's great, but I don't want to add another UX since we already have, like, five!

@nadahlberg

Copy link
Copy Markdown
Member Author

@mlissner I'm trying to conform this to webhooks but noticing another issue: Webhooks gets its data from a DRF endpoint which counts against the user's rate limit. So if a user has hit their limit for any reason then they'll get an error when visiting the webhooks tab. If they've hit their hourly limit, this page could be blocked for the whole hour.

One workaround is to add throttle_classes = [] to WebhooksViewSet and WebhookEventViewSet (and the analogous OAuthAuthorizationsViewSet we create). Does this seem like the right approach? Is there any risk removing these API views from the rate limit system given that they are internal / for UI only?

@mlissner

Copy link
Copy Markdown
Member

I'm not sure, honestly. I'd have to dig in, but maybe we can ask Alberto when he gets back?

@vasilyslevin

Copy link
Copy Markdown

@mlissner I'm trying to conform this to webhooks but noticing another issue: Webhooks gets its data from a DRF endpoint which counts against the user's rate limit. So if a user has hit their limit for any reason then they'll get an error when visiting the webhooks tab. If they've hit their hourly limit, this page could be blocked for the whole hour.

One workaround is to add throttle_classes = [] to WebhooksViewSet and WebhookEventViewSet (and the analogous OAuthAuthorizationsViewSet we create). Does this seem like the right approach? Is there any risk removing these API views from the rate limit system given that they are internal / for UI only?

Agreed on throttle_classes = []. These viewsets are session-authenticated
and serve only the user's own data. There is no abuse vector. A user cannot
enumerate other users' OAuth apps.

Webhooks: Maybe add a comment on the viewset explaining why throttling
is off, so future devs don't restore it by accident.

On UX: If the rate-limit problem also affects the existing
webhooks tab, maybe opening a separate issue would be better.

@mlissner mlissner requested a review from albertisfu May 20, 2026 16:57
@mlissner

Copy link
Copy Markdown
Member

Alberto, I think we just need your thoughts here, when you have a moment.

@albertisfu albertisfu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great, @nadahlberg. I just left a couple of questions/suggestions.

Regarding webhooks, yeah, right now the new UI throttles are causing an issue. The solution is indeed to add throttle_classes = [] to WebhooksViewSet and WebhookEventViewSet.

However, I'm worried that a user could figure out the webhooks endpoint URL (it's not currently listed in /api/rest/v4/, but they could still find it by inspecting the code) and start making automated requests and if this endpoint is not throttled, it could be abused.

So I'd also suggest removing token access for this endpoint so that only session auth (via the website) is allowed. For that, you can override authentication_classes in WebhooksViewSet and WebhookEventViewSet with:

authentication_classes = [ReplicaRoutingSessionAuthentication]

Considering that ReplicaRoutingSessionAuthentication is currently the default session auth class.

I found this is also a problem with the Tags UI. However, since that endpoint is publicly available, the solution might be different. I'll open a separate issue to review it independently.

Comment thread cl/users/views.py
tok.application_id,
{
"application": tok.application,
"first_granted": tok.created,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this label accurate for the value being displayed?

I'm not very familiar with OAuth, but I see we have ACCESS_TOKEN_EXPIRE_SECONDS set to 1 hour and REFRESH_TOKEN_EXPIRE_SECONDS set to 30 days. Does that mean the token displayed here is reissued every hour?

If so, would it be better to label this as something Last Issued instead?

Comment thread cl/users/views.py
{
"application": tok.application,
"first_granted": tok.created,
"last_used": tok.updated,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the token updated field refreshed on each call, or does this refer to the last time the token was last refreshed?

@mlissner

Copy link
Copy Markdown
Member

@nadahlberg, we're getting a few errors related to the webhooks website failing. Can we get this one merged?

@nadahlberg nadahlberg moved this from To Do to F 🔌 in Sprint (Web Team) Jun 9, 2026
@ERosendo ERosendo moved this from F 🔌 to To Do in Sprint (Web Team) Jun 9, 2026
@nadahlberg

Copy link
Copy Markdown
Member Author

Hi all, this one is blocked because we want to diagnose why so many oauth apps are being created per user before we add the applications tab to the developer tools page. Instead I'm going to close this and break it up accordingly:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Add branded template to OAuth consent page

5 participants