VYPR
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.

PackageAffected versionsPatched versions
django-allauthPyPI
< 65.13.065.13.0

Affected products

1

Patches

2
39f4a4ce9c89

fix(idp): device grant now checks user.is_active

https://github.com/pennersr/django-allauthRaymond PennersOct 30, 2025via ghsa
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()
    
c54edf947c5a

fix(idp): reject tokens with inactive users

https://github.com/pennersr/django-allauthRaymond PennersOct 30, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.