diff --git a/packages/console/src/components/SessionExpired/index.tsx b/packages/console/src/components/SessionExpired/index.tsx
index 03bb1bc67..9e243b0a2 100644
--- a/packages/console/src/components/SessionExpired/index.tsx
+++ b/packages/console/src/components/SessionExpired/index.tsx
@@ -1,8 +1,7 @@
import { useLogto } from '@logto/react';
-import { useContext, useEffect } from 'react';
+import { useEffect } from 'react';
-import { getCallbackUrl } from '@/consts';
-import { TenantsContext } from '@/contexts/TenantsProvider';
+import useRedirectUri from '@/hooks/use-redirect-uri';
import { saveRedirect } from '@/utils/storage';
import AppLoading from '../AppLoading';
@@ -10,14 +9,14 @@ import AppLoading from '../AppLoading';
/** This component shows a loading indicator and tries to sign in again. */
function SessionExpired() {
const { signIn, isLoading } = useLogto();
- const { currentTenantId } = useContext(TenantsContext);
+ const redirectUri = useRedirectUri();
useEffect(() => {
if (!isLoading) {
saveRedirect();
- void signIn(getCallbackUrl(currentTenantId).href);
+ void signIn(redirectUri.href);
}
- }, [signIn, isLoading, currentTenantId]);
+ }, [signIn, isLoading, redirectUri]);
return ;
}
diff --git a/packages/console/src/consts/tenants.ts b/packages/console/src/consts/tenants.ts
index 1e00e9a07..e7430d377 100644
--- a/packages/console/src/consts/tenants.ts
+++ b/packages/console/src/consts/tenants.ts
@@ -1,6 +1,3 @@
-import { ossConsolePath } from '@logto/schemas';
-import { conditionalArray } from '@silverhand/essentials';
-
import { adminEndpoint, isCloud } from './env';
const getAdminTenantEndpoint = () => {
@@ -17,12 +14,3 @@ const getAdminTenantEndpoint = () => {
export const adminTenantEndpoint = getAdminTenantEndpoint();
export const mainTitle = isCloud ? 'Logto Cloud' : 'Logto Console';
-
-export const getCallbackUrl = (tenantId?: string) =>
- new URL(
- // Only Cloud has tenantId in callback URL
- '/' + conditionalArray(isCloud ? tenantId : 'console', 'callback').join('/'),
- window.location.origin
- );
-
-export const getSignOutRedirectPathname = () => (isCloud ? '/' : ossConsolePath);
diff --git a/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx b/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx
index c3a3a8639..7dd2a1c25 100644
--- a/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx
+++ b/packages/console/src/containers/AppContent/components/Topbar/UserInfo/index.tsx
@@ -11,12 +11,12 @@ import Profile from '@/assets/icons/profile.svg';
import SignOut from '@/assets/icons/sign-out.svg';
import UserAvatar from '@/components/UserAvatar';
import UserInfoCard from '@/components/UserInfoCard';
-import { getSignOutRedirectPathname } from '@/consts';
import Divider from '@/ds-components/Divider';
import Dropdown, { DropdownItem } from '@/ds-components/Dropdown';
import Spacer from '@/ds-components/Spacer';
import { Ring as Spinner } from '@/ds-components/Spinner';
import useCurrentUser from '@/hooks/use-current-user';
+import useRedirectUri from '@/hooks/use-redirect-uri';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import useUserPreferences from '@/hooks/use-user-preferences';
import { DynamicAppearanceMode } from '@/types/appearance-mode';
@@ -33,6 +33,7 @@ function UserInfo() {
const { user, isLoading: isLoadingUser } = useCurrentUser();
const anchorRef = useRef(null);
const [showDropdown, setShowDropdown] = useState(false);
+ const postSignOutRedirectUri = useRedirectUri('signOut');
const [isLoading, setIsLoading] = useState(false);
const {
@@ -128,7 +129,7 @@ function UserInfo() {
return;
}
setIsLoading(true);
- void signOut(new URL(getSignOutRedirectPathname(), window.location.origin).toString());
+ void signOut(postSignOutRedirectUri.href);
}}
>
{t('menu.sign_out')}
diff --git a/packages/console/src/containers/ProtectedRoutes/index.tsx b/packages/console/src/containers/ProtectedRoutes/index.tsx
index b2799306e..c2e7d5205 100644
--- a/packages/console/src/containers/ProtectedRoutes/index.tsx
+++ b/packages/console/src/containers/ProtectedRoutes/index.tsx
@@ -5,8 +5,9 @@ import { Outlet, useSearchParams } from 'react-router-dom';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
import AppLoading from '@/components/AppLoading';
-import { searchKeys, getCallbackUrl } from '@/consts';
+import { searchKeys } from '@/consts';
import { TenantsContext } from '@/contexts/TenantsProvider';
+import useRedirectUri from '@/hooks/use-redirect-uri';
import { saveRedirect } from '@/utils/storage';
/**
@@ -32,15 +33,16 @@ export default function ProtectedRoutes() {
const api = useCloudApi();
const [searchParameters] = useSearchParams();
const { isAuthenticated, isLoading, signIn } = useLogto();
- const { currentTenantId, isInitComplete, resetTenants } = useContext(TenantsContext);
+ const { isInitComplete, resetTenants } = useContext(TenantsContext);
+ const redirectUri = useRedirectUri();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
saveRedirect();
const isSignUpMode = yes(searchParameters.get(searchKeys.signUp));
- void signIn(getCallbackUrl(currentTenantId).href, conditional(isSignUpMode && 'signUp'));
+ void signIn(redirectUri.href, conditional(isSignUpMode && 'signUp'));
}
- }, [currentTenantId, isAuthenticated, isLoading, searchParameters, signIn]);
+ }, [redirectUri, isAuthenticated, isLoading, searchParameters, signIn]);
useEffect(() => {
if (isAuthenticated && !isInitComplete) {
diff --git a/packages/console/src/hooks/use-redirect-uri.ts b/packages/console/src/hooks/use-redirect-uri.ts
new file mode 100644
index 000000000..25708075f
--- /dev/null
+++ b/packages/console/src/hooks/use-redirect-uri.ts
@@ -0,0 +1,20 @@
+import { ossConsolePath } from '@logto/schemas';
+import { conditionalArray } from '@silverhand/essentials';
+import { useHref } from 'react-router-dom';
+
+import { isCloud } from '@/consts/env';
+
+/**
+ * The hook that returns the absolute URL for the sign-in or sign-out callback.
+ * The path is not related to react-router, which means the path will also include
+ * the basename of react-router if it's set.
+ */
+const useRedirectUri = (flow: 'signIn' | 'signOut' = 'signIn') => {
+ const path = useHref(
+ conditionalArray(!isCloud && ossConsolePath, flow === 'signIn' && '/callback').join('')
+ );
+
+ return new URL(path, window.location.origin);
+};
+
+export default useRedirectUri;
diff --git a/packages/console/src/pages/Welcome/index.tsx b/packages/console/src/pages/Welcome/index.tsx
index 051fc10e6..f2d920b8b 100644
--- a/packages/console/src/pages/Welcome/index.tsx
+++ b/packages/console/src/pages/Welcome/index.tsx
@@ -4,8 +4,8 @@ import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Logo from '@/assets/images/logo.svg';
-import { getCallbackUrl } from '@/consts';
import Button from '@/ds-components/Button';
+import useRedirectUri from '@/hooks/use-redirect-uri';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import useTheme from '@/hooks/use-theme';
@@ -16,6 +16,7 @@ function Welcome() {
const { navigate } = useTenantPathname();
const { isAuthenticated, signIn } = useLogto();
const theme = useTheme();
+ const redirectUri = useRedirectUri();
useEffect(() => {
// If authenticated, navigate to the console root page directly
@@ -40,7 +41,7 @@ function Welcome() {
type="branding"
title="welcome.create_account"
onClick={() => {
- void signIn(getCallbackUrl().href);
+ void signIn(redirectUri.href);
}}
/>