From e958b10f75252a2ecb563012f018e15397012374 Mon Sep 17 00:00:00 2001 From: Sn0rrii <4687675+netscout2001@users.noreply.github.com> Date: Sun, 19 Apr 2026 08:10:56 +0200 Subject: [PATCH] fix(oidc): raise callback code/state max_length from 512 to 2048 (#1024) Facebook and some other OAuth providers issue authorization codes that exceed 512 characters. Pydantic rejected these with 422 string_too_long. The OAuth spec defines no maximum code length; 2048 aligns with common provider limits. Also adds three integration tests to verify 512-char and 2048-char codes are accepted while 2049-char codes are correctly rejected. --- backend/app/api/routes/mfa.py | 4 +- backend/tests/integration/test_mfa_api.py | 45 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/backend/app/api/routes/mfa.py b/backend/app/api/routes/mfa.py index c68830fda..cd53465b6 100644 --- a/backend/app/api/routes/mfa.py +++ b/backend/app/api/routes/mfa.py @@ -1189,8 +1189,8 @@ async def oidc_authorize( @router.get("/oidc/callback") async def oidc_callback( - code: str | None = Query(default=None, max_length=512), - state: str | None = Query(default=None, max_length=512), + code: str | None = Query(default=None, max_length=2048), + state: str | None = Query(default=None, max_length=2048), error: str | None = Query(default=None, max_length=256), db: AsyncSession = Depends(get_db), ) -> RedirectResponse: diff --git a/backend/tests/integration/test_mfa_api.py b/backend/tests/integration/test_mfa_api.py index 409381bac..d7fe1a543 100644 --- a/backend/tests/integration/test_mfa_api.py +++ b/backend/tests/integration/test_mfa_api.py @@ -3030,9 +3030,7 @@ class TestOIDCIssuerUrlTrailingSlash: @pytest.mark.asyncio @pytest.mark.integration - async def test_trailing_slash_issuer_url_fetches_correct_discovery_url( - self, async_client: AsyncClient - ): + async def test_trailing_slash_issuer_url_fetches_correct_discovery_url(self, async_client: AsyncClient): from unittest.mock import AsyncMock, MagicMock, patch issuer_with_slash = "https://authentik.example.com/application/o/bambuddy/" @@ -3082,9 +3080,7 @@ class TestOIDCIssuerUrlTrailingSlash: @pytest.mark.asyncio @pytest.mark.integration - async def test_iss_claim_trailing_slash_accepted( - self, async_client: AsyncClient, db_session: AsyncSession - ): + async def test_iss_claim_trailing_slash_accepted(self, async_client: AsyncClient, db_session: AsyncSession): """Provider configured without trailing slash, Authentik JWT iss has trailing slash. Both sides must be normalised before comparison so the login succeeds. @@ -3197,3 +3193,40 @@ class TestOIDCIssuerUrlTrailingSlash: "Trailing slash mismatch in iss claim must not cause token_validation_failed" ) assert "oidc_token=" in location, f"Expected oidc_token in redirect, got: {location}" + + +class TestOIDCCallbackCodeLength: + """OIDC callback code query param must accept up to 2048 characters (OAuth spec).""" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_code_512_chars_accepted(self, async_client: AsyncClient): + """A 512-character code (old limit) must not be rejected with 422.""" + code = "a" * 512 + resp = await async_client.get( + f"/api/v1/auth/oidc/callback?code={code}&state=bogus-state", + follow_redirects=False, + ) + assert resp.status_code != 422, "512-char code must not be rejected by Pydantic" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_code_2048_chars_accepted(self, async_client: AsyncClient): + """A 2048-character code must not be rejected with 422.""" + code = "a" * 2048 + resp = await async_client.get( + f"/api/v1/auth/oidc/callback?code={code}&state=bogus-state", + follow_redirects=False, + ) + assert resp.status_code != 422, "2048-char code must not be rejected by Pydantic" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_code_2049_chars_rejected(self, async_client: AsyncClient): + """A 2049-character code must be rejected with 422.""" + code = "a" * 2049 + resp = await async_client.get( + f"/api/v1/auth/oidc/callback?code={code}&state=bogus-state", + follow_redirects=False, + ) + assert resp.status_code == 422, "2049-char code must be rejected by Pydantic"