Skip to content

Commit 54aab76

Browse files
authored
Merge pull request #2929 from Shopify/fix/basename-authentication-loop
[shopify-app-react-router][patch] Fix authentication loop for apps with React Router basename
2 parents 60acae1 + 6287e42 commit 54aab76

File tree

5 files changed

+60
-3
lines changed

5 files changed

+60
-3
lines changed

.changeset/old-ideas-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/shopify-app-react-router': patch
3+
---
4+
5+
Resolve bug loading embedded app in POS when using React Router basename

packages/apps/shopify-app-react-router/src/server/authenticate/admin/__tests__/doc-request-path.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,29 @@ describe('authorize.admin doc request path', () => {
5050
expect(response.status).toBe(500);
5151
});
5252

53+
it('throws an error if the request URL is the login path with basename', async () => {
54+
// GIVEN
55+
// Simulates an app with React Router basename="/base-path"
56+
// When navigating to the login route, the URL becomes /base-path/auth/login
57+
const shopify = shopifyApp(testConfig({authPathPrefix: '/auth'}));
58+
const request = new Request(`${APP_URL}/base-path/auth/login`);
59+
60+
// WHEN
61+
const response = await getThrownResponse(
62+
shopify.authenticate.admin,
63+
request,
64+
);
65+
66+
// THEN
67+
// SHOULD throw 500 error with helpful message
68+
expect(response.status).toBe(500);
69+
const errorMessage = await response.text();
70+
expect(errorMessage).toContain(
71+
'Detected call to shopify.authenticate.admin() from configured login path',
72+
);
73+
expect(errorMessage).toContain('/auth/login');
74+
});
75+
5376
it('redirects to the bounce page URL if id_token search param is missing', async () => {
5477
// GIVEN
5578
const shopify = shopifyApp(testConfig());

packages/apps/shopify-app-react-router/src/server/authenticate/admin/__tests__/patch-session-token-path.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,33 @@ describe('authorize.admin path session token path', () => {
5555
`<script data-api-key="${config.apiKey}" src="${APP_BRIDGE_URL}"></script>`,
5656
);
5757
});
58+
59+
test('Uses AppBridge to get a session token when URL includes a basename before authPathPrefix', async () => {
60+
// GIVEN
61+
// Simulates an app deployed with React Router basename="/base-path"
62+
// When React Router redirects to "/auth/session-token", the browser URL becomes "/base-path/auth/session-token"
63+
const authPathPrefix = '/auth';
64+
const config = testConfig({authPathPrefix});
65+
const shopify = shopifyApp(config);
66+
67+
// WHEN
68+
// This is the URL that the browser actually requests after React Router processes the redirect
69+
// The pathname includes the basename (/base-path) + the auth path (/auth/session-token)
70+
const url = `${APP_URL}/base-path/auth/session-token?shop=${TEST_SHOP}`;
71+
const response = await getThrownResponse(
72+
shopify.authenticate.admin,
73+
new Request(url),
74+
);
75+
76+
// THEN
77+
// Should render the bounce page (AppBridge script) even though pathname doesn't exactly match config.auth.patchSessionTokenPath
78+
expect(response.status).toBe(200);
79+
expectDocumentRequestHeaders(response, config.isEmbeddedApp);
80+
expect(response.headers.get('content-type')).toBe(
81+
'text/html;charset=utf-8',
82+
);
83+
expect((await response.text()).trim()).toBe(
84+
`<script data-api-key="${config.apiKey}" src="${APP_BRIDGE_URL}"></script>`,
85+
);
86+
});
5887
});

packages/apps/shopify-app-react-router/src/server/authenticate/admin/authenticate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function authStrategyFactory<ConfigArg extends AppConfigArg>({
5858
async function respondToBouncePageRequest(request: Request) {
5959
const url = new URL(request.url);
6060

61-
if (url.pathname === config.auth.patchSessionTokenPath) {
61+
if (url.pathname.endsWith(config.auth.patchSessionTokenPath)) {
6262
logger.debug('Rendering bounce page', {
6363
shop: getShopFromRequest(request),
6464
});
@@ -69,7 +69,7 @@ export function authStrategyFactory<ConfigArg extends AppConfigArg>({
6969
async function respondToExitIframeRequest(request: Request) {
7070
const url = new URL(request.url);
7171

72-
if (url.pathname === config.auth.exitIframePath) {
72+
if (url.pathname.endsWith(config.auth.exitIframePath)) {
7373
const destination = url.searchParams.get('exitIframe')!;
7474

7575
logger.debug('Rendering exit iframe page', {

packages/apps/shopify-app-react-router/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function redirectToLoginPath(request: Request, params: BasicParams): never {
3333
const {config, logger} = params;
3434

3535
const {pathname} = new URL(request.url);
36-
if (pathname === config.auth.loginPath) {
36+
if (pathname.endsWith(config.auth.loginPath)) {
3737
const message =
3838
`Detected call to shopify.authenticate.admin() from configured login path ` +
3939
`('${config.auth.loginPath}'), please make sure to call shopify.login() from that route instead.`;

0 commit comments

Comments
 (0)