goauthentik / goauthentik/authentik

OAuth2 token ViewSets bypass RBAC — get_queryset hardcodes is_superuser, unlike AuthenticatedSessionViewSet

Open
#24,374 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
25.6k
Forks
2k
Avg merge
1d 1h
Merged PRs (30d)
644

Description

### Is your feature request related to a problem?

Yes — least-privilege service accounts cannot manage OAuth2 tokens. On authentik 2026.5.3, `RefreshTokenViewSet` / `AccessTokenViewSet` / `AuthorizationCodeViewSet` (`authentik/providers/oauth2/api/tokens.py`) implement:

```python
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(user=user.pk)
```

This runs before the RBAC layer, so a service account holding the **global `authentik_providers_oauth2.view_refreshtoken` / `delete_refreshtoken` permissions** still only ever sees its own tokens. By contrast, `AuthenticatedSessionViewSet` respects the generic RBAC `ObjectFilter` / `ObjectPermissions` — global `view_/delete_authenticatedsession` grants work as expected there.

Concrete use case: an application backend implementing **"log out all devices"** for its users. Killing sessions works with a tightly-scoped Role, but session deletion intentionally spares refresh tokens (`RefreshToken.session` is `SET_DEFAULT` — persisting `offline_access` past session termination is deliberate and documented in the model), so *completing* the logout requires revoking the user's refresh tokens — which today forces the application backend to hold a **full superuser token**, a much larger blast radius than the operation needs.

### Describe the solution you'd like

Honor object-level RBAC on the OAuth2 token ViewSets the same way `AuthenticatedSessionViewSet` does (drop the `is_superuser` short-circuit in `get_queryset()` in favor of the `ObjectFilter` path), so a Role granting `view_`/`delete_` on refresh tokens can administer them.

A bulk "revoke all tokens for user X" action would additionally simplify the logout-all case (sessions have `bulk_delete`; the token ViewSets have no equivalent), but the RBAC fix alone is enough.

### Describe alternatives that you've considered

- **Superuser-bound API token held by the application backend**, tightly encapsulated (dedicated service account, network isolation, every call audited) — works, and is what we'll do meanwhile, but it grants far more than the operation needs.
- **RFC 7009 revocation endpoint** — only revokes a token the client presents; the backend does not hold other devices' refresh tokens, so logout-all cannot be built on it.
- **Deleting sessions only** — deliberately spares `offline_access` refresh tokens, so a stolen refresh token would survive the user pressing "log out everywhere".

### Additional context

Version: authentik 2026.5.3, self-hosted (official helm chart). Verified against the source at tag `version/2026.5.3`: `authentik/providers/oauth2/api/tokens.py` `get_queryset` (L101-105, byte-identical on current `main`); the sessions-side comparison is `authentik/core/api/authenticated_sessions.py` + `authentik/rbac/filters.py::ObjectFilter`. Happy to provide more detail or test a patch.

*(Research assisted by AI; all cited code paths were verified by hand against the tag.)*

Contributor guide

Open the contributing guide

Research direction

Start in authentik/providers/oauth2/api/tokens.py and compare the token ViewSets with AuthenticatedSessionViewSet in authentik/core/api/authenticated_sessions.py. Read the ObjectFilter path in authentik/rbac/filters.py, then identify the relevant existing API tests before changing behavior. Done means appropriately scoped global view_ and delete_ permissions can administer OAuth2 tokens without requiring superuser access.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, authorization, security
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.