feat(oauth): add branded oath consent page template#7340
Conversation
|
@claude review once |
|
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? |
| <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> |
There was a problem hiding this comment.
🔴 "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
- A user grants an application access via this consent screen and is redirected back to the third-party app.
- 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).
- The link reads
<a href="{% url "view_api" %}">revoke access</a>. Django reversesview_api→/profile/api/. - The user lands on
/profile/api/, which (perprofile/api.htmllines 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. - 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.
|
@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:
Success on revokation state:
Mobile:
Also fixed the other two smaller issues Claude raised. |
|
We have a bunch of user experiences around deletion in our profiles:
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! |
|
@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 |
|
I'm not sure, honestly. I'd have to dig in, but maybe we can ask Alberto when he gets back? |
Agreed on throttle_classes = []. These viewsets are session-authenticated Webhooks: Maybe add a comment on the viewset explaining why throttling On UX: If the rate-limit problem also affects the existing |
|
Alberto, I think we just need your thoughts here, when you have a moment. |
albertisfu
left a comment
There was a problem hiding this comment.
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.
| tok.application_id, | ||
| { | ||
| "application": tok.application, | ||
| "first_granted": tok.created, |
There was a problem hiding this comment.
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?
| { | ||
| "application": tok.application, | ||
| "first_granted": tok.created, | ||
| "last_used": tok.updated, |
There was a problem hiding this comment.
Is the token updated field refreshed on each call, or does this refer to the last time the token was last refreshed?
|
@nadahlberg, we're getting a few errors related to the webhooks website failing. Can we get this one merged? |
|
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:
|



Fixes
Fixes #7228
Summary
This PR adds a template to render the
/authorizeroute 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-deployskip-celery-deployskip-cronjob-deployskip-daemon-deployScreenshots
Desktop
Mobile