VYPR
High severityNVD Advisory· Published Jul 23, 2025· Updated Jul 23, 2025

authentik has an insufficient check for account active status during OAuth/SAML authentication

CVE-2025-53942

Description

authentik is an open-source Identity Provider that emphasizes flexibility and versatility, with support for a wide set of protocols. In versions 2025.4.4 and earlier, as well as versions 2025.6.0-rc1 through 2025.6.3, deactivated users who registered through OAuth/SAML or linked their accounts to OAuth/SAML providers can still retain partial access to the system despite their accounts being deactivated. They end up in a half-authenticated state where they cannot access the API but crucially they can authorize applications if they know the URL of the application. To workaround this issue, developers can add an expression policy to the user login stage on the respective authentication flow with the expression of return request.context["pending_user"].is_active. This modification ensures that the return statement only activates the user login stage when the user is active. This issue is fixed in versions authentik 2025.4.4 and 2025.6.4.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Deactivated OAuth/SAML-linked users in authentik can still authorize apps, fixed in versions 2025.4.4 and 2025.6.4.

CVE-2025-53942 affects authentik, an open-source Identity Provider. The vulnerability allows deactivated users who registered or linked accounts via OAuth/SAML to retain partial access to the system. Despite being deactivated, these users can still authorize applications if they know the application URL, bypassing the deactivation status [4].

To exploit this, an attacker must have a deactivated authentik account that was originally created through or linked to an OAuth/SAML provider. The attacker then needs to know the URL of an application configured in authentik. While the user cannot access the API, authorization grants remain functional, enabling the attacker to authorize applications without being fully authenticated [1][2][3].

The impact is that a deactivated user can still authorize applications, which could lead to unauthorized access or actions within the ecosystem, depending on the permissions of the authorized application. This undermines the security of user deactivation.

The issue is fixed in authentik versions 2025.4.4 and 2025.6.4 [4]. A workaround involves adding an expression policy to the user login stage to check if the pending user is active. Administrators are advised to upgrade to the patched versions immediately [1][2][3][4].

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
goauthentik.ioGo
< 0.0.0-20250722122105-7a4c6b9b50f80.0.0-20250722122105-7a4c6b9b50f8

Affected products

2
  • Range: <=2025.4.4, >=2025.6.0-rc1 <=2025.6.3
  • goauthentik/authentikv5
    Range: <= 2025.4.3, < 2025.4.4

Patches

3
c3629d12bfe3

security: fix CVE-2025-53942 (#15719)

6 files changed · +85 3
  • authentik/core/middleware.py+6 0 modified
    @@ -5,6 +5,7 @@
     from functools import partial
     from uuid import uuid4
     
    +from django.contrib.auth import logout
     from django.contrib.auth.models import AnonymousUser
     from django.core.exceptions import ImproperlyConfigured
     from django.http import HttpRequest, HttpResponse
    @@ -58,6 +59,11 @@ def process_request(self, request):
             request.user = SimpleLazyObject(lambda: get_user(request))
             request.auser = partial(aget_user, request)
     
    +        user = request.user
    +        if user and user.is_authenticated and not user.is_active:
    +            logout(request)
    +            raise AssertionError()
    +
     
     class ImpersonateMiddleware:
         """Middleware to impersonate users"""
    
  • authentik/stages/password/tests.py+23 0 modified
    @@ -89,6 +89,29 @@ def test_valid_password(self):
             self.assertEqual(response.status_code, 200)
             self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
     
    +    def test_valid_password_inactive(self):
    +        """Test with a valid pending user and valid password"""
    +        self.user.is_active = False
    +        self.user.save()
    +        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
    +        plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
    +        session = self.client.session
    +        session[SESSION_KEY_PLAN] = plan
    +        session.save()
    +
    +        response = self.client.post(
    +            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
    +            # Form data
    +            {"password": self.user.username},
    +        )
    +
    +        self.assertEqual(response.status_code, 200)
    +        self.assertStageResponse(
    +            response,
    +            self.flow,
    +            response_errors={"password": [{"string": "Invalid password", "code": "invalid"}]},
    +        )
    +
         def test_invalid_password(self):
             """Test with a valid pending user and invalid password"""
             plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
    
  • authentik/stages/user_login/stage.py+1 0 modified
    @@ -91,6 +91,7 @@ def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse
             user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
             if not user.is_active:
                 self.logger.warning("User is not active, login will not work.")
    +            return self.executor.stage_invalid()
             delta = self.set_session_duration(remember)
             self.set_session_ip()
             # the `user_logged_in` signal will update the user to write the `last_login` field
    
  • authentik/stages/user_login/tests.py+21 2 modified
    @@ -6,6 +6,7 @@
     from django.urls import reverse
     from django.utils.timezone import now
     
    +from authentik.blueprints.tests import apply_blueprint
     from authentik.core.models import AuthenticatedSession, Session
     from authentik.core.tests.utils import create_test_admin_user, create_test_flow
     from authentik.flows.markers import StageMarker
    @@ -174,6 +175,7 @@ def test_without_user(self):
                 component="ak-stage-access-denied",
             )
     
    +    @apply_blueprint("default/flow-default-user-settings-flow.yaml")
         def test_inactive_account(self):
             """Test with a valid pending user and backend"""
             self.user.is_active = False
    @@ -187,8 +189,25 @@ def test_inactive_account(self):
             response = self.client.get(
                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
             )
    -
             self.assertEqual(response.status_code, 200)
    -        self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
    +        self.assertStageResponse(
    +            response, self.flow, component="ak-stage-access-denied", error_message="Unknown error"
    +        )
    +
    +        # Check that API requests get rejected
             response = self.client.get(reverse("authentik_api:application-list"))
             self.assertEqual(response.status_code, 403)
    +
    +        # Check that flow requests requiring a user also get rejected
    +        response = self.client.get(
    +            reverse(
    +                "authentik_api:flow-executor",
    +                kwargs={"flow_slug": "default-user-settings-flow"},
    +            )
    +        )
    +        self.assertStageResponse(
    +            response,
    +            self.flow,
    +            component="ak-stage-access-denied",
    +            error_message="Flow does not apply to current user.",
    +        )
    
  • website/docs/security/cves/CVE-2025-53942.md+29 0 added
    @@ -0,0 +1,29 @@
    +# CVE-2025-53942
    +
    +_Reported by [@pascalwei](https://github.com/pascalwei)_
    +
    +## Insufficient check for account active status when authenticating with OAuth/SAML Sources
    +
    +### Summary
    +
    +Deactivated users that had either enrolled via OAuth/SAML or had their account connected to an OAuth/SAML account can still partially access authentik even if their account is deactivated. They end up in a half-authenticated state where they cannot access the API but crucially they can authorize applications if they know the URL of the application.
    +
    +### Patches
    +
    +authentik 2025.4.4 and 2025.6.4 fix this issue.
    +
    +### Workarounds
    +
    +Adding an expression policy to the user login stage on the respective authentication flow with the expression of
    +
    +```py
    +return request.context["pending_user"].is_active
    +```
    +
    +This expression will only activate the user login stage when the user is active.
    +
    +### For more information
    +
    +If you have any questions or comments about this advisory:
    +
    +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io).
    
  • website/sidebars.js+5 1 modified
    @@ -716,7 +716,11 @@ export default {
                             {
                                 type: "category",
                                 label: "2025",
    -                            items: ["security/cves/CVE-2025-52553", "security/cves/CVE-2025-29928"],
    +                            items: [
    +                                "security/cves/CVE-2025-53942",
    +                                "security/cves/CVE-2025-52553",
    +                                "security/cves/CVE-2025-29928",
    +                            ],
                             },
                             {
                                 type: "category",
    
ce3f9e3763c1

security: fix CVE-2025-53942 (#15719)

7 files changed · +94 3
  • authentik/core/middleware.py+6 0 modified
    @@ -5,6 +5,7 @@
     from functools import partial
     from uuid import uuid4
     
    +from django.contrib.auth import logout
     from django.contrib.auth.models import AnonymousUser
     from django.core.exceptions import ImproperlyConfigured
     from django.http import HttpRequest, HttpResponse
    @@ -58,6 +59,11 @@ def process_request(self, request):
             request.user = SimpleLazyObject(lambda: get_user(request))
             request.auser = partial(aget_user, request)
     
    +        user = request.user
    +        if user and user.is_authenticated and not user.is_active:
    +            logout(request)
    +            raise AssertionError()
    +
     
     class ImpersonateMiddleware:
         """Middleware to impersonate users"""
    
  • authentik/stages/password/tests.py+23 0 modified
    @@ -89,6 +89,29 @@ def test_valid_password(self):
             self.assertEqual(response.status_code, 200)
             self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
     
    +    def test_valid_password_inactive(self):
    +        """Test with a valid pending user and valid password"""
    +        self.user.is_active = False
    +        self.user.save()
    +        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
    +        plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
    +        session = self.client.session
    +        session[SESSION_KEY_PLAN] = plan
    +        session.save()
    +
    +        response = self.client.post(
    +            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
    +            # Form data
    +            {"password": self.user.username},
    +        )
    +
    +        self.assertEqual(response.status_code, 200)
    +        self.assertStageResponse(
    +            response,
    +            self.flow,
    +            response_errors={"password": [{"string": "Invalid password", "code": "invalid"}]},
    +        )
    +
         def test_invalid_password(self):
             """Test with a valid pending user and invalid password"""
             plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
    
  • authentik/stages/user_login/stage.py+1 0 modified
    @@ -91,6 +91,7 @@ def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse
             user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
             if not user.is_active:
                 self.logger.warning("User is not active, login will not work.")
    +            return self.executor.stage_invalid()
             delta = self.set_session_duration(remember)
             self.set_session_ip()
             # the `user_logged_in` signal will update the user to write the `last_login` field
    
  • authentik/stages/user_login/tests.py+21 2 modified
    @@ -7,6 +7,7 @@
     from django.urls import reverse
     from django.utils.timezone import now
     
    +from authentik.blueprints.tests import apply_blueprint
     from authentik.core.models import AuthenticatedSession, Session
     from authentik.core.tests.utils import create_test_admin_user, create_test_flow
     from authentik.flows.markers import StageMarker
    @@ -180,6 +181,7 @@ def test_without_user(self):
                 component="ak-stage-access-denied",
             )
     
    +    @apply_blueprint("default/flow-default-user-settings-flow.yaml")
         def test_inactive_account(self):
             """Test with a valid pending user and backend"""
             self.user.is_active = False
    @@ -193,12 +195,29 @@ def test_inactive_account(self):
             response = self.client.get(
                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
             )
    -
             self.assertEqual(response.status_code, 200)
    -        self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
    +        self.assertStageResponse(
    +            response, self.flow, component="ak-stage-access-denied", error_message="Unknown error"
    +        )
    +
    +        # Check that API requests get rejected
             response = self.client.get(reverse("authentik_api:application-list"))
             self.assertEqual(response.status_code, 403)
     
    +        # Check that flow requests requiring a user also get rejected
    +        response = self.client.get(
    +            reverse(
    +                "authentik_api:flow-executor",
    +                kwargs={"flow_slug": "default-user-settings-flow"},
    +            )
    +        )
    +        self.assertStageResponse(
    +            response,
    +            self.flow,
    +            component="ak-stage-access-denied",
    +            error_message="Flow does not apply to current user.",
    +        )
    +
         def test_binding_net_break_log(self):
             """Test logout_extra with exception"""
             # IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json
    
  • website/docs/releases/2025/v2025.6.md+9 0 modified
    @@ -169,6 +169,15 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2025.6
     - web/elements: typing error when variables are not converted to string (cherry-pick #15169) (#15222)
     - web/user: fix infinite loop when no user settings flow is set (cherry-pick #15188) (#15192)
     
    +## Fixed in 2025.6.4
    +
    +- core: fix missing serializer on AuthenticatedSession (cherry-pick #15323) (#15365)
    +- core: fix set_token_key permission not declared (cherry-pick #15384) (#15391)
    +- providers/proxy: fix ingress-nginx proxy buffer size annotations (cherry-pick #15506) (#15507)
    +- security: fix CVE-2025-53942 (#15719)
    +- web/admin: fix nested table pagination and search (#15385)
    +- web/elements: fix table search not resetting page when query changes (cherry-pick #15324) (#15326)
    +
     ## API Changes
     
     #### What's New
    
  • website/docs/security/cves/CVE-2025-53942.md+29 0 added
    @@ -0,0 +1,29 @@
    +# CVE-2025-53942
    +
    +_Reported by [@pascalwei](https://github.com/pascalwei)_
    +
    +## Insufficient check for account active status when authenticating with OAuth/SAML Sources
    +
    +### Summary
    +
    +Deactivated users that had either enrolled via OAuth/SAML or had their account connected to an OAuth/SAML account can still partially access authentik even if their account is deactivated. They end up in a half-authenticated state where they cannot access the API but crucially they can authorize applications if they know the URL of the application.
    +
    +### Patches
    +
    +authentik 2025.4.4 and 2025.6.4 fix this issue.
    +
    +### Workarounds
    +
    +Adding an expression policy to the user login stage on the respective authentication flow with the expression of
    +
    +```py
    +return request.context["pending_user"].is_active
    +```
    +
    +This expression will only activate the user login stage when the user is active.
    +
    +### For more information
    +
    +If you have any questions or comments about this advisory:
    +
    +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io).
    
  • website/sidebars/docs.mjs+5 1 modified
    @@ -715,7 +715,11 @@ const items = [
                         {
                             type: "category",
                             label: "2025",
    -                        items: ["security/cves/CVE-2025-52553", "security/cves/CVE-2025-29928"],
    +                        items: [
    +                            "security/cves/CVE-2025-53942",
    +                            "security/cves/CVE-2025-52553",
    +                            "security/cves/CVE-2025-29928",
    +                        ],
                         },
                         {
                             type: "category",
    
7a4c6b9b50f8

security: fix CVE-2025-53942 (#15719)

https://github.com/goauthentik/authentikJens L.Jul 22, 2025via ghsa
6 files changed · +85 3
  • authentik/core/middleware.py+6 0 modified
    @@ -5,6 +5,7 @@
     from functools import partial
     from uuid import uuid4
     
    +from django.contrib.auth import logout
     from django.contrib.auth.models import AnonymousUser
     from django.core.exceptions import ImproperlyConfigured
     from django.http import HttpRequest, HttpResponse
    @@ -58,6 +59,11 @@ def process_request(self, request):
             request.user = SimpleLazyObject(lambda: get_user(request))
             request.auser = partial(aget_user, request)
     
    +        user = request.user
    +        if user and user.is_authenticated and not user.is_active:
    +            logout(request)
    +            raise AssertionError()
    +
     
     class ImpersonateMiddleware:
         """Middleware to impersonate users"""
    
  • authentik/stages/password/tests.py+23 0 modified
    @@ -89,6 +89,29 @@ def test_valid_password(self):
             self.assertEqual(response.status_code, 200)
             self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
     
    +    def test_valid_password_inactive(self):
    +        """Test with a valid pending user and valid password"""
    +        self.user.is_active = False
    +        self.user.save()
    +        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
    +        plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
    +        session = self.client.session
    +        session[SESSION_KEY_PLAN] = plan
    +        session.save()
    +
    +        response = self.client.post(
    +            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
    +            # Form data
    +            {"password": self.user.username},
    +        )
    +
    +        self.assertEqual(response.status_code, 200)
    +        self.assertStageResponse(
    +            response,
    +            self.flow,
    +            response_errors={"password": [{"string": "Invalid password", "code": "invalid"}]},
    +        )
    +
         def test_invalid_password(self):
             """Test with a valid pending user and invalid password"""
             plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
    
  • authentik/stages/user_login/stage.py+1 0 modified
    @@ -153,6 +153,7 @@ def do_login(self, request: HttpRequest, remember: bool | None = None) -> HttpRe
             user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
             if not user.is_active:
                 self.logger.warning("User is not active, login will not work.")
    +            return self.executor.stage_invalid()
             delta = self.set_session_duration(bool(remember))
             self.set_session_ip()
             # Check if the login request is coming from a known device
    
  • authentik/stages/user_login/tests.py+21 2 modified
    @@ -7,6 +7,7 @@
     from django.urls import reverse
     from django.utils.timezone import now
     
    +from authentik.blueprints.tests import apply_blueprint
     from authentik.core.models import AuthenticatedSession, Session
     from authentik.core.tests.utils import create_test_flow, create_test_user
     from authentik.flows.markers import StageMarker
    @@ -181,6 +182,7 @@ def test_without_user(self):
                 component="ak-stage-access-denied",
             )
     
    +    @apply_blueprint("default/flow-default-user-settings-flow.yaml")
         def test_inactive_account(self):
             """Test with a valid pending user and backend"""
             self.user.is_active = False
    @@ -194,12 +196,29 @@ def test_inactive_account(self):
             response = self.client.get(
                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
             )
    -
             self.assertEqual(response.status_code, 200)
    -        self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
    +        self.assertStageResponse(
    +            response, self.flow, component="ak-stage-access-denied", error_message="Unknown error"
    +        )
    +
    +        # Check that API requests get rejected
             response = self.client.get(reverse("authentik_api:application-list"))
             self.assertEqual(response.status_code, 403)
     
    +        # Check that flow requests requiring a user also get rejected
    +        response = self.client.get(
    +            reverse(
    +                "authentik_api:flow-executor",
    +                kwargs={"flow_slug": "default-user-settings-flow"},
    +            )
    +        )
    +        self.assertStageResponse(
    +            response,
    +            self.flow,
    +            component="ak-stage-access-denied",
    +            error_message="Flow does not apply to current user.",
    +        )
    +
         def test_binding_net_break_log(self):
             """Test logout_extra with exception"""
             # IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json
    
  • website/docs/security/cves/CVE-2025-53942.md+29 0 added
    @@ -0,0 +1,29 @@
    +# CVE-2025-53942
    +
    +_Reported by [@pascalwei](https://github.com/pascalwei)_
    +
    +## Insufficient check for account active status when authenticating with OAuth/SAML Sources
    +
    +### Summary
    +
    +Deactivated users that had either enrolled via OAuth/SAML or had their account connected to an OAuth/SAML account can still partially access authentik even if their account is deactivated. They end up in a half-authenticated state where they cannot access the API but crucially they can authorize applications if they know the URL of the application.
    +
    +### Patches
    +
    +authentik 2025.4.4 and 2025.6.4 fix this issue.
    +
    +### Workarounds
    +
    +Adding an expression policy to the user login stage on the respective authentication flow with the expression of
    +
    +```py
    +return request.context["pending_user"].is_active
    +```
    +
    +This expression will only activate the user login stage when the user is active.
    +
    +### For more information
    +
    +If you have any questions or comments about this advisory:
    +
    +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io).
    
  • website/docs/sidebar.mjs+5 1 modified
    @@ -726,7 +726,11 @@ const items = [
                         {
                             type: "category",
                             label: "2025",
    -                        items: ["security/cves/CVE-2025-52553", "security/cves/CVE-2025-29928"],
    +                        items: [
    +                            "security/cves/CVE-2025-53942",
    +                            "security/cves/CVE-2025-52553",
    +                            "security/cves/CVE-2025-29928",
    +                        ],
                         },
                         {
                             type: "category",
    

Vulnerability mechanics

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