diff --git a/packages/apps/shopify-app-remix/src/server/__test-helpers/test-config.ts b/packages/apps/shopify-app-remix/src/server/__test-helpers/test-config.ts index 76df94cb78..d1f27e2e30 100644 --- a/packages/apps/shopify-app-remix/src/server/__test-helpers/test-config.ts +++ b/packages/apps/shopify-app-remix/src/server/__test-helpers/test-config.ts @@ -15,6 +15,7 @@ import {testConfig as testConfigImport} from '../test-helpers/test-config'; const TEST_FUTURE_FLAGS: Required<{[key in keyof FutureFlags]: true}> = { unstable_newEmbeddedAuthStrategy: true, removeRest: true, + remixSingleFetch: true, } as const; // Override the helper's future flags and logger settings for our purposes diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts index 06643067de..c1809bb6fb 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts @@ -131,7 +131,7 @@ describe('authorize.admin doc request path', () => { ); }); - it('throws a 401 if app is embedded and the id_token search param is invalid for XHR requests', async () => { + it('throws a 302 if app is embedded and the id_token search param is invalid for XHR requests', async () => { // GIVEN const shopify = shopifyApp(testConfig()); await setUpValidSession(shopify.sessionStorage); @@ -145,7 +145,7 @@ describe('authorize.admin doc request path', () => { ); // THEN - expect(response.status).toBe(401); + expect(response.status).toBe(302); expect( response.headers.get('X-Shopify-Retry-Invalid-Session-Request'), ).toBe('1'); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts index 29ad79ae03..f44bb4d61c 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts @@ -14,7 +14,7 @@ import { describe('authorize.session token header path', () => { describe('errors', () => { - it('throws a 401 if the session token is invalid', async () => { + it('throws a 302 if the session token is invalid', async () => { // GIVEN const shopify = shopifyApp(testConfig()); @@ -27,7 +27,7 @@ describe('authorize.session token header path', () => { ); // THEN - expect(response.status).toBe(401); + expect(response.status).toBe(302); }); }); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/cancel.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/cancel.test.ts index d10c0c2644..45fadac9bc 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/cancel.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/cancel.test.ts @@ -180,7 +180,7 @@ describe('Cancel billing', () => { response.headers.get(REAUTH_URL_HEADER)!, ); - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); expect(origin).toEqual(APP_URL); expect(pathname).toEqual('/auth'); }); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/check.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/check.test.ts index 907bd411c1..3c1b7a52fc 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/check.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/check.test.ts @@ -201,7 +201,7 @@ describe('Billing check', () => { // THEN const reauthUrl = new URL(response.headers.get(REAUTH_URL_HEADER)!); - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); expect(reauthUrl.origin).toEqual(APP_URL); expect(reauthUrl.pathname).toEqual('/auth'); }); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts index cd92439802..9c0f9aaff9 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts @@ -247,7 +247,7 @@ describe('Create usage record', () => { response.headers.get(REAUTH_URL_HEADER)!, ); - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); expect(origin).toEqual(APP_URL); expect(pathname).toEqual('/auth'); }); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/request.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/request.test.ts index e2f6ecfdf9..17c3547067 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/request.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/request.test.ts @@ -162,7 +162,7 @@ describe('Billing request', () => { ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); expect(response.headers.get(REAUTH_URL_HEADER)).toEqual( responses.CONFIRMATION_URL, ); @@ -267,7 +267,7 @@ describe('Billing request', () => { ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); const reauthUrl = new URL(response.headers.get(REAUTH_URL_HEADER)!); expect(reauthUrl.origin).toEqual(APP_URL); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/require.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/require.test.ts index abd2918f61..97f85f3f4e 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/require.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/require.test.ts @@ -227,7 +227,7 @@ describe('Billing require', () => { // THEN const reauthUrl = new URL(response.headers.get(REAUTH_URL_HEADER)!); - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); expect(reauthUrl.origin).toEqual(APP_URL); expect(reauthUrl.pathname).toEqual('/auth'); }); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/update-usage-subscription-capped-amount.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/update-usage-subscription-capped-amount.test.ts index ae0604bc5f..463132d60c 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/update-usage-subscription-capped-amount.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/update-usage-subscription-capped-amount.test.ts @@ -150,7 +150,7 @@ describe('Update usage billing plan capped amount', () => { ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); expect(response.headers.get(REAUTH_URL_HEADER)).toEqual( responses.CONFIRMATION_URL, ); @@ -270,7 +270,7 @@ describe('Update usage billing plan capped amount', () => { ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); const reauthUrl = new URL(response.headers.get(REAUTH_URL_HEADER)!); expect(reauthUrl.origin).toEqual(APP_URL); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/helpers.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/helpers.ts index 3b234aef7e..d304a15a37 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/helpers.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/helpers.ts @@ -18,15 +18,17 @@ export function redirectOutOfApp( const isXhrRequest = request.headers.get('authorization'); if (isXhrRequest) { - // eslint-disable-next-line no-warning-comments - // TODO Check this with the beta flag disabled (with the bounce page) - // Remix is not including the X-Shopify-API-Request-Failure-Reauthorize-Url when throwing a Response - // https://github.com/remix-run/remix/issues/5356 - throw new Response(undefined, { - status: 401, - statusText: 'Unauthorized', - headers: getAppBridgeHeaders(url), - }); + if (config.future.remixSingleFetch) { + throw redirect(url, { + headers: getAppBridgeHeaders(url), + }); + } else { + throw new Response(undefined, { + status: 401, + statusText: 'Unauthorized', + headers: getAppBridgeHeaders(url), + }); + } } else if (isEmbeddedRequest) { const params = new URLSearchParams({ shop, diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/__tests__/redirect.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/__tests__/redirect.test.ts index 4edb8db6fb..62079a4f9a 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/__tests__/redirect.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/__tests__/redirect.test.ts @@ -376,7 +376,7 @@ describe('Redirect helper', () => { } async function assertAppBridgeHeaders(response: Response, url: string) { - expect(response.status).toBe(401); + expect(response.status).toBe(302); expect(response.headers.get(REAUTH_URL_HEADER)).toBe(url); } diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts index f55b2c2876..b07fda6669 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts @@ -2,7 +2,10 @@ import type {BasicParams} from '../../../types'; import {beginAuth} from './begin-auth'; import {redirectWithExitIframe} from './redirect-with-exitiframe'; -import {redirectWithAppBridgeHeaders} from './redirect-with-app-bridge-headers'; +import { + redirectWithAppBridgeHeaders, + redirectWithResponseWithAppBridgeHeaders, +} from './redirect-with-app-bridge-headers'; export async function redirectToAuthPage( params: BasicParams, @@ -19,7 +22,11 @@ export async function redirectToAuthPage( if (isXhrRequest) { const redirectUri = new URL(config.auth.path, config.appUrl); redirectUri.searchParams.set('shop', shop); - redirectWithAppBridgeHeaders(redirectUri.toString()); + if (config.future.remixSingleFetch) { + redirectWithAppBridgeHeaders(redirectUri.toString()); + } else { + redirectWithResponseWithAppBridgeHeaders(redirectUri.toString()); + } } else if (isEmbeddedRequest) { redirectWithExitIframe(params, request, shop); } else { diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-install-page.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-install-page.ts index 1fdec3d09b..2410319fea 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-install-page.ts @@ -2,7 +2,10 @@ import {redirect as remixRedirect} from '@remix-run/server-runtime'; import type {BasicParams} from '../../../types'; -import {redirectWithAppBridgeHeaders} from './redirect-with-app-bridge-headers'; +import { + redirectWithAppBridgeHeaders, + redirectWithResponseWithAppBridgeHeaders, +} from './redirect-with-app-bridge-headers'; export async function redirectToInstallPage( params: BasicParams, @@ -11,7 +14,11 @@ export async function redirectToInstallPage( ): Promise { const installUrl = buildInstallUrl(params, shop, optionalScopes); if (params.config.isEmbeddedApp) { - throw redirectWithAppBridgeHeaders(installUrl); + if (params.config.future.remixSingleFetch) { + throw redirectWithAppBridgeHeaders(installUrl); + } else { + throw redirectWithResponseWithAppBridgeHeaders(installUrl); + } } else { throw remixRedirect(installUrl); } diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts index fb89970b07..99867d5ccf 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts @@ -1,6 +1,16 @@ +import {redirect} from '@remix-run/server-runtime'; + import {REAUTH_URL_HEADER} from '../../const'; export function redirectWithAppBridgeHeaders(redirectUri: string): never { + throw redirect(redirectUri, { + headers: getAppBridgeHeaders(redirectUri), + }); +} + +export function redirectWithResponseWithAppBridgeHeaders( + redirectUri: string, +): never { throw new Response(undefined, { status: 401, statusText: 'Unauthorized', diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts index 8cdb1964cc..5e48dceafd 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts @@ -7,7 +7,10 @@ import {BasicParams} from '../../../types'; import {getSessionTokenHeader} from '../../helpers/get-session-token-header'; import {renderAppBridge} from './render-app-bridge'; -import {redirectWithAppBridgeHeaders} from './redirect-with-app-bridge-headers'; +import { + redirectWithAppBridgeHeaders, + redirectWithResponseWithAppBridgeHeaders, +} from './redirect-with-app-bridge-headers'; export type RedirectTarget = '_self' | '_parent' | '_top' | '_blank'; export type RedirectInit = number | (ResponseInit & {target?: RedirectTarget}); @@ -67,7 +70,11 @@ export function redirectFactory( return remixRedirect(parsedUrl.toString(), init); } } else if (isDataRequest(request)) { - throw redirectWithAppBridgeHeaders(parsedUrl.toString()); + if (config.future.remixSingleFetch) { + throw redirectWithAppBridgeHeaders(parsedUrl.toString()); + } else { + throw redirectWithResponseWithAppBridgeHeaders(parsedUrl.toString()); + } } else if (isEmbeddedRequest(request)) { throw renderAppBridge(params, request, { url: parsedUrl.toString(), diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/query.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/query.test.ts index b5b4dd195c..5607d1f7b6 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/query.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/query.test.ts @@ -68,7 +68,7 @@ it('returns app bridge redirection during request headers when Shopify invalidat ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); const {origin, pathname, searchParams} = new URL( response.headers.get(REAUTH_URL_HEADER)!, diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/request.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/request.test.ts index 97daf3b267..8b9b1b340c 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/request.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/request.test.ts @@ -139,7 +139,7 @@ describe('request from an embedded app', () => { ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); const reuthorizeHeader = response.headers.get( 'x-shopify-api-request-failure-reauthorize-url', ); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/revoke.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/revoke.test.ts index fe97d86184..4bf26e2532 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/revoke.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/scope/__tests__/revoke.test.ts @@ -107,7 +107,7 @@ it('returns app bridge redirection during request headers when Shopify invalidat ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); const {origin, pathname, searchParams} = new URL( response.headers.get(REAUTH_URL_HEADER)!, diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts index b215fbf50c..f3073da609 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts @@ -113,7 +113,7 @@ describe('admin.authenticate context', () => { ); // THEN - expect(response.status).toEqual(401); + expect(response.status).toEqual(302); const {origin, pathname, searchParams} = new URL( response.headers.get(REAUTH_URL_HEADER)!, diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts index 5dd2f2eb67..f330bdda3f 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts @@ -36,7 +36,7 @@ describe('authorize.session token header path', () => { response.headers.get(REAUTH_URL_HEADER)!, ); - expect(response.status).toBe(401); + expect(response.status).toBe(302); expect(origin).toBe(APP_URL); expect(pathname).toBe('/auth'); expect(searchParams.get('shop')).toBe(TEST_SHOP); @@ -67,7 +67,7 @@ describe('authorize.session token header path', () => { const {origin, pathname, searchParams} = new URL( response.headers.get(REAUTH_URL_HEADER)!, ); - expect(response.status).toBe(401); + expect(response.status).toBe(302); expect(origin).toBe(APP_URL); expect(pathname).toBe('/auth'); expect(searchParams.get('shop')).toBe(TEST_SHOP); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts index 9fc4765e34..cd44e60e7a 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts @@ -188,7 +188,7 @@ describe('authenticate', () => { ); }); - test('throws 401 unauthorized on XHR request when receiving an invalid subject token response from token exchange API', async () => { + test('throws 302 unauthorized on XHR request when receiving an invalid subject token response from token exchange API', async () => { // GIVEN const config = testConfig(); const shopify = shopifyApp(config); @@ -207,7 +207,7 @@ describe('authenticate', () => { ); // THEN - expect(response.status).toBe(401); + expect(response.status).toBe(302); expect( response.headers.get('X-Shopify-Retry-Invalid-Session-Request'), ).toEqual('1'); diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts b/packages/apps/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts index e81e67b088..d37fb843fc 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts @@ -1,3 +1,5 @@ +import {redirect} from '@remix-run/server-runtime'; + import {redirectToBouncePage} from '../admin/helpers/redirect-to-bounce-page'; import {RETRY_INVALID_SESSION_HEADER} from '../const'; import {BasicParams} from '../../types'; @@ -20,9 +22,18 @@ export function respondToInvalidSessionToken({ return redirectToBouncePage({api, logger, config}, new URL(request.url)); } + if (retryRequest) { + logger.debug('Retrying request after invalid session token', { + requestUrl: request.url, + }); + throw redirect(request.url, { + headers: RETRY_INVALID_SESSION_HEADER, + }); + } + throw new Response(undefined, { status: 401, statusText: 'Unauthorized', - headers: retryRequest ? RETRY_INVALID_SESSION_HEADER : {}, + headers: {}, }); } diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/public/checkout/__tests__/authenticate.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/public/checkout/__tests__/authenticate.test.ts index 00236b035b..c52fa631dd 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/public/checkout/__tests__/authenticate.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/public/checkout/__tests__/authenticate.test.ts @@ -109,7 +109,7 @@ describe('JWT validation', () => { expect(response.status).toBe(401); }); - it('throws a 401 on invalid Authorization bearer token', async () => { + it('throws a 302 on invalid Authorization bearer token', async () => { // GIVEN const shopify = shopifyApp(testConfig()); @@ -122,7 +122,7 @@ describe('JWT validation', () => { ); // THEN - expect(response.status).toBe(401); + expect(response.status).toBe(302); }); it('rejects bot requests', async () => { diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/public/customer-account/__tests__/authenticate.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/public/customer-account/__tests__/authenticate.test.ts index 24bfa97249..df3fbb5ade 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/public/customer-account/__tests__/authenticate.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/public/customer-account/__tests__/authenticate.test.ts @@ -108,7 +108,7 @@ describe('JWT validation', () => { expect(response.status).toBe(401); }); - it('throws a 401 on invalid Authorization bearer token', async () => { + it('throws a 302 on invalid Authorization bearer token', async () => { // GIVEN const shopify = shopifyApp(testConfig()); @@ -121,7 +121,7 @@ describe('JWT validation', () => { ); // THEN - expect(response.status).toBe(401); + expect(response.status).toBe(302); }); it('rejects bot requests', async () => { diff --git a/packages/apps/shopify-app-remix/src/server/future/flags.ts b/packages/apps/shopify-app-remix/src/server/future/flags.ts index 4229128d35..731071c7d4 100644 --- a/packages/apps/shopify-app-remix/src/server/future/flags.ts +++ b/packages/apps/shopify-app-remix/src/server/future/flags.ts @@ -38,6 +38,16 @@ export interface FutureFlags { * @default false */ removeRest?: boolean; + + /** + * When enabled, introduces compatibility with Remix v3_singleFetch future flag. + * + * In the cases of billing redirects, and retrying request a request after an invalid session token + * the library will now throw a 302 redirect response instead of a 401 unauthorized response. + * + * @default false + */ + remixSingleFetch?: boolean; } // When adding new flags, use this format: @@ -76,4 +86,11 @@ export function logDisabledFutureFlags( '\n Your app must be using Shopify managed install: https://shopify.dev/docs/apps/auth/installation', ); } + + if (!config.future.remixSingleFetch) { + logFlag( + 'remixSingleFetch', + 'Enable this to use Remix v3_singleFetch future flag.', + ); + } }