@W-22846346@ Refresh and retry on SCAPI 401 instead of a hard error page - #3860
Merged
Conversation
The commerce-sdk-react Auth layer decides whether to refresh purely from the expiry indicator (the cc-at-expires cookie in HttpOnly mode, or the JWT exp in localStorage mode), never from a 401 response. When the access token is invalid-but-unexpired (server-revoked, tampered, or after a SLAS signing-key rotation), the indicator still reads "valid", so ready() sends the bad token, SCAPI returns 401, and the SSR render path throws an HTTPError that the PWA Kit error boundary renders as a hard "This page isn't working - HTTPError 401" page -- even though a still-valid refresh token (cc-nx) usually exists. handleInvalidToken now adds 401-driven refresh-and-retry: for any 401 that isn't the "Customer credentials changed after token was issued." case, it clears the stale cc-at-expires indicator and refreshes, mirroring the existing 400 access_token_cookie_missing handler. Clearing the expiry first is required so _refreshAccessToken() doesn't short-circuit on the still-"valid" indicator. The caller (useAuthorizationHeader and the useQuery/useMutation custom-endpoint paths) retries the request exactly once, so a repeat 401 still propagates. The fix is mode-agnostic, covering both HttpOnly and localStorage; the gap was confirmed to reproduce in both. No MRT/proxy changes: the refresh round-trip, cookie rewrite, and bearer injection this relies on are already deployed and exercised by the normal expiry-driven refresh.
|
Git2Gus App is installed but the |
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
unandyala
approved these changes
Jun 5, 2026
shethj
approved these changes
Jun 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The
commerce-sdk-reactAuthlayer decides whether to refresh purely from the expiry indicator — the non-HttpOnlycc-at-expirescookie in HttpOnly mode, or the JWTexpin localStorage mode — and never from a401response.When the access token is invalid-but-unexpired (server-revoked, tampered, or invalidated after a SLAS signing-key rotation), the indicator still reads "valid", so
Auth.ready()sends the bad token, SCAPI returns401, and the SSR render path throws anHTTPErrorthat the PWA Kit error boundary renders as a hard "This page isn't working — HTTPError 401" page — even though a still-valid refresh token (cc-nx) usually exists.This adds 401-driven refresh-and-retry at the existing choke point,
handleInvalidToken. For any401that isn't the"Customer credentials changed after token was issued."case, it clears the stalecc-at-expiresindicator and refreshes — mirroring the existing400 access_token_cookie_missinghandler. Clearing the expiry first is required so_refreshAccessToken()doesn't short-circuit on the still-"valid" indicator and return the stale token. The caller (useAuthorizationHeader, and theuseQuery/useMutationcustom-endpoint paths) retries the request exactly once with no.catchon the retry, so a repeat401still propagates — no loop.Fail-closed behavior is preserved (no 5xx, no data leak); this only changes a previously hard-failing
401into a graceful refresh-and-retry.Not HttpOnly-specific: the gap was confirmed to reproduce in a non-HttpOnly (localStorage) environment too — it's a pre-existing SDK resilience gap (the SDK refreshes on expiry, not on a 401). HttpOnly mode amplifies it because the client can't validate the opaque token client-side. The fix lives in the shared retry path, so it covers both modes.
No MRT / proxy changes: the refresh round-trip, the cookie rewrite on the refresh response, and the bearer injection on the retried SCAPI call are all already deployed and exercised by the normal expiry-driven refresh. Adoption is a normal SDK-version bump + storefront redeploy.
Types of Changes
Changes
handleInvalidToken(packages/commerce-sdk-react/src/hooks/helpers.ts): a generic401(not"Customer credentials changed…") now callsauth.clearAccessTokenExpiry()thenauth.refreshAccessToken()and returns the fresh token for the caller's single retry, instead of re-throwing.refreshAccessToken()falls back to a guest token (the same fallbackready()and the400handler already rely on) rather than throwing.helpers.test.ts): rewrote the obsolete "re-throws generic 401" test to assert clear-expiry + refresh + single read of the response body +clear-before-refreshordering; added a test that arefreshAccessToken()rejection propagates; upgraded the error mock so the one-shot body read is verifiable. UpdatedhandleInvalidTokenJSDoc to document all branches.How to Test-Drive This PR
commerce-sdk-react(HttpOnly or localStorage mode), authenticate so a valid session exists.cc-atvalue (or simulate a server-side revocation / SLAS key rotation) while leavingcc-at-expiresintact.cc-nx, retries once, and the page renders normally.Checklists
General
Accessibility Compliance
Localization