Moderate severityGHSA Advisory· Published Dec 15, 2025· Updated Dec 15, 2025
CVE-2025-65430
CVE-2025-65430
Description
An issue was discovered in allauth-django before 65.13.0. IdP: marking a user as is_active=False after having handed tokens for that user while the account was still active had no effect. Fixed the access/refresh tokens are now rejected.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
django-allauthPyPI | < 65.13.0 | 65.13.0 |
Affected products
1- Range: < 65.13.0
Patches
239f4a4ce9c89fix(idp): device grant now checks user.is_active
2 files changed · +27 −14
allauth/idp/oidc/internal/oauthlib/device_codes.py+10 −4 modified@@ -1,6 +1,8 @@ import time -from typing import List, Optional, Tuple +from typing import Dict, List, Optional, Tuple +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AbstractBaseUser from django.core.cache import cache from django.http import HttpRequest @@ -17,7 +19,7 @@ ) from allauth.account.adapter import get_adapter as get_account_adapter -from allauth.account.internal.userkit import user_id_to_str +from allauth.account.internal.userkit import str_to_user_id, user_id_to_str from allauth.core.internal.cryptokit import compare_user_code from allauth.idp.oidc.models import Client @@ -95,7 +97,7 @@ def update_device_state(device_code: str, data: dict) -> bool: def poll_device_code( request: HttpRequest, -) -> dict: +) -> Tuple[AbstractBaseUser, Dict]: client_id = request.POST.get("client_id") device_code = request.POST.get("device_code") if not client_id or not device_code: @@ -121,4 +123,8 @@ def poll_device_code( raise AccessDenied assert granted is True # nosec cache.delete(cache_key) - return data + + user = get_user_model().objects.filter(pk=str_to_user_id(data["user"])).first() + if user is None or not user.is_active: + raise AccessDenied + return user, data
allauth/idp/oidc/views.py+17 −10 modified@@ -1,8 +1,9 @@ from http import HTTPStatus from typing import List, Optional -from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model +from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import AbstractBaseUser from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import PermissionDenied from django.core.signing import BadSignature, Signer @@ -29,7 +30,6 @@ from allauth.account import app_settings as account_settings from allauth.account.adapter import get_adapter as get_account_adapter from allauth.account.internal.decorators import login_not_required -from allauth.account.internal.userkit import str_to_user_id from allauth.core.internal import jwkkit from allauth.core.internal.httpkit import add_query_params, del_query_params from allauth.idp.oidc import app_settings @@ -362,31 +362,38 @@ def post(self, request): return self._post_device_token(request) return self._create_token_response(request) - def _create_token_response(self, request, data: Optional[dict] = None): + def _create_token_response( + self, + request, + *, + user: Optional[AbstractBaseUser] = None, + data: Optional[dict] = None, + ): orequest = extract_params(request) oresponse = get_server( - pre_token=[lambda orequest: self._pre_token(orequest, data)] + pre_token=[lambda orequest: self._pre_token(orequest, user, data)] ).create_token_response(*orequest) return convert_response(*oresponse) - def _pre_token(self, orequest, data: Optional[dict]): + def _pre_token( + self, orequest, user: Optional[AbstractBaseUser], data: Optional[dict] + ): if orequest.grant_type == Client.GrantType.DEVICE_CODE: + assert user is not None # nosec assert data is not None # nosec if scope := data.get("scope"): orequest.scope = scope - orequest.user = get_user_model().objects.get( - pk=str_to_user_id(data["user"]) - ) + orequest.user = user def _post_device_token(self, request): try: - data = device_codes.poll_device_code(request) + user, data = device_codes.poll_device_code(request) except OAuth2Error as e: return HttpResponse( e.json, content_type="application/json", status=e.status_code ) else: - return self._create_token_response(request, data) + return self._create_token_response(request, user=user, data=data) token = TokenView.as_view()
c54edf947c5afix(idp): reject tokens with inactive users
2 files changed · +14 −0
allauth/idp/oidc/internal/oauthlib/request_validator.py+4 −0 modified@@ -260,6 +260,8 @@ def validate_bearer_token(self, token, scopes, request) -> bool: instance = Token.objects.lookup(Token.Type.ACCESS_TOKEN, token) if not instance: return False + if not instance.user or not instance.user.is_active: + return False granted_scopes = instance.get_scopes() if not set(scopes).issubset(set(granted_scopes)): return False @@ -315,6 +317,8 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs ) if not token: return False + if not token.user or not token.user.is_active: + return False request.user = token.user request.refresh_token_instance = token return True
tests/apps/idp/oidc/contrib/ninja/test_views.py+10 −0 modified@@ -22,3 +22,13 @@ def test_resource_forbidden(db, client, access_token_generator, user, oidc_clien ) resp = client.get("/idp/ninja/resource", HTTP_AUTHORIZATION=f"bearer {token}") assert resp.status_code == HTTPStatus.UNAUTHORIZED + + +def test_resource_user_inactive(db, client, access_token_generator, user, oidc_client): + user.is_active = False + user.save(update_fields=["is_active"]) + token, _ = access_token_generator( + client=oidc_client, user=user, scopes=["view-resource"] + ) + resp = client.get("/idp/ninja/resource", HTTP_AUTHORIZATION=f"bearer {token}") + assert resp.status_code == HTTPStatus.UNAUTHORIZED
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-qhmc-3mvr-f2j4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-65430ghsaADVISORY
- allauth.org/news/2025/10/django-allauth-65.13.0-releasedghsaWEB
- github.com/pennersr/django-allauth/commit/39f4a4ce9c891795b00914ca5ec32de72d5369c0ghsaWEB
- github.com/pennersr/django-allauth/commit/c54edf947c5a1c8c4ff3cddb75c86000ecb2507dghsaWEB
- allauth.org/news/2025/10/django-allauth-65.13.0-released/mitre
News mentions
0No linked articles in our index yet.