diff --git a/.changes/unreleased/Fixed-20260720-000000.yaml b/.changes/unreleased/Fixed-20260720-000000.yaml new file mode 100644 index 000000000..a2f2db5e4 --- /dev/null +++ b/.changes/unreleased/Fixed-20260720-000000.yaml @@ -0,0 +1,3 @@ +kind: Fixed +body: 'oauth2provider: use the website base path when building frontend redirect URLs instead of the API base path.' +time: 2026-07-20T00:00:00.000000+00:00 diff --git a/supertokens_python/recipe/oauth2provider/recipe_implementation.py b/supertokens_python/recipe/oauth2provider/recipe_implementation.py index 80b45ccd8..968a6b20e 100644 --- a/supertokens_python/recipe/oauth2provider/recipe_implementation.py +++ b/supertokens_python/recipe/oauth2provider/recipe_implementation.py @@ -788,7 +788,7 @@ async def get_frontend_redirection_url( website_domain = self.app_info.get_origin( None, user_context ).get_as_string_dangerous() - website_base_path = self.app_info.api_base_path.get_as_string_dangerous() + website_base_path = self.app_info.website_base_path.get_as_string_dangerous() if isinstance(params, FrontendRedirectionURLTypeLogin): query_params: Dict[str, str] = {"loginChallenge": params.login_challenge} diff --git a/tests/oauth2provider/test_frontend_redirect.py b/tests/oauth2provider/test_frontend_redirect.py new file mode 100644 index 000000000..d835215b4 --- /dev/null +++ b/tests/oauth2provider/test_frontend_redirect.py @@ -0,0 +1,103 @@ +# Copyright (c) 2026, VRAI Labs and/or its affiliates. All rights reserved. +# +# This software is licensed under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from unittest.mock import MagicMock +from typing import Union + +from pytest import mark + +from supertokens_python.recipe.oauth2provider.interfaces import ( + FrontendRedirectionURLTypeLogin, + FrontendRedirectionURLTypeLogoutConfirmation, + FrontendRedirectionURLTypePostLogoutFallback, + FrontendRedirectionURLTypeTryRefresh, +) +from supertokens_python.recipe.oauth2provider.recipe_implementation import ( + RecipeImplementation, +) +from supertokens_python.supertokens import AppInfo + +pytestmark = mark.asyncio + +FrontendRedirectParams = Union[ + FrontendRedirectionURLTypeTryRefresh, + FrontendRedirectionURLTypeLogoutConfirmation, + FrontendRedirectionURLTypePostLogoutFallback, +] + + +def get_recipe_implementation() -> RecipeImplementation: + app_info = AppInfo( + app_name="test-app", + api_domain="https://api.example.com", + website_domain="https://www.example.com", + framework="fastapi", + api_gateway_path="", + api_base_path="/auth", + website_base_path="/account/login", + mode=None, + origin=None, + ) + return RecipeImplementation( + querier=MagicMock(), + app_info=app_info, + get_default_access_token_payload=MagicMock(), + get_default_id_token_payload=MagicMock(), + get_default_user_info_payload=MagicMock(), + ) + + +async def test_frontend_login_redirect_uses_website_base_path(): + recipe_implementation = get_recipe_implementation() + + redirect_to = await recipe_implementation.get_frontend_redirection_url( + FrontendRedirectionURLTypeLogin( + login_challenge="login-challenge", + tenant_id="public", + force_fresh_auth=False, + ), + user_context={}, + ) + + assert redirect_to == ( + "https://www.example.com/account/login?loginChallenge=login-challenge" + ) + + +@mark.parametrize( + "params, expected_suffix", + [ + ( + FrontendRedirectionURLTypeTryRefresh("login-challenge"), + "/try-refresh?loginChallenge=login-challenge", + ), + ( + FrontendRedirectionURLTypeLogoutConfirmation("logout-challenge"), + "/oauth/logout?logoutChallenge=logout-challenge", + ), + ( + FrontendRedirectionURLTypePostLogoutFallback(), + "", + ), + ], +) +async def test_frontend_redirects_use_website_base_path( + params: FrontendRedirectParams, expected_suffix: str +): + recipe_implementation = get_recipe_implementation() + + redirect_to = await recipe_implementation.get_frontend_redirection_url( + params, + user_context={}, + ) + + assert redirect_to == f"https://www.example.com/account/login{expected_suffix}"