fix(auth): end sessions the server has rejected (auth-public-client ^0.6.0) - #467
Draft
interacsean wants to merge 2 commits into
Draft
fix(auth): end sessions the server has rejected (auth-public-client ^0.6.0)#467interacsean wants to merge 2 commits into
interacsean wants to merge 2 commits into
Conversation
Raises @tailor-platform/auth-public-client from ^0.5.1 to ^0.6.0.
When a grant expired or was revoked, 0.5.x left isAuthenticated true and
reattached the dead token to every request, stranding apps in a permanent
gateway-unauthorized loop that only a manual IndexedDB clear recovered
from (planning#1495, auth-public-client#113). 0.6.0 acts on any
token-endpoint rejection and emits logout + auth_state_changed, which is
already what AuthProvider reacts to — so the fix lands without a source
change here. Transient failures (5xx, timeout, network) still leave the
session intact.
Consumers who detected dead sessions by catching
Error("No valid access token") lose that signal: fetch and
getAuthHeaders now throw the underlying error. Called out in the
changeset.
- add a regression test covering the 0.6.0 teardown event sequence
(logout followed by auth_state_changed) yielding exactly one login
- document session-expiry behaviour in the authentication guide
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
/review |
Contributor
|
✅ Code Review completed successfully! Code review complete — no issues found. PR #467 is a clean dependency bump of |
Contributor
Code Metrics Report
Details | | main (41ae0e3) | #467 (6c3cac1) | +/- |
|---------------------|----------------|----------------|------|
| Coverage | 90.1% | 90.1% | 0.0% |
| Files | 152 | 152 | 0 |
| Lines | 5201 | 5201 | 0 |
| Covered | 4690 | 4690 | 0 |
+ | Test Execution Time | 2m5s | 1m59s | -6s |Code coverage of files in pull request scope (0.0% → 0.0%)
Reported by octocov |
Two issues from adversarial review of this PR.
The regression test was a tautology. Asserting only the final login count
passes even with the auth_state_changed filter removed, because
loginInFlightRef absorbs the duplicate call — verified by injecting that
exact regression and watching the test stay green. It now asserts that a
`logout` event alone is inert before firing auth_state_changed, which is
what makes it discriminating; the same mutation now fails it.
The transient-failure guarantee was wrong. isAuthoritativeRefreshRejection
keys off the response shape, not the underlying cause: oauth4webapi builds
a ResponseBodyError for any 4xx carrying an `error` code, and 0.6.0 treats
everything except use_dpop_nonce as authoritative. So
400 {"error":"server_error"} ends the session, as does a 5xx carrying a
WWW-Authenticate header (checkOAuthBodyError runs the challenge check
before the 4xx-only body parse). Only a timeout, a network failure, and a
bare 5xx are actually safe. Docs and changeset now say that instead of
promising blip tolerance the code does not deliver.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
Raises
@tailor-platform/auth-public-clientfrom^0.5.1to^0.6.0, so a session the server has refused is torn down instead of replayed forever.Why
On 0.5.x an expired or revoked grant left
isAuthenticatedtrue and reattached the dead token to every request. Apps sat in a permanent{"errors":[{"message":"unauthorized","type":"Gateway"}]}loop that only a manual IndexedDB clear recovered from. Two paths kept the dead session alive: rejection was recognised only by three literal error codes (so aWWW-Authenticatechallenge, which carries no.error, was missed entirely), and the proactive refresh flattened the rejection tonullbefore any code could act on it.^0.5.1does not span0.6.0, so consumers do not pick this up automatically — the pin is the delivery mechanism.No source change was needed — verified, not assumed
Reading 0.6.0's shipped code rather than trusting the release notes:
logoutImplemitslogoutthenauth_state_changed.resetStateleavesisReady: truewithisAuthenticated: false.That is exactly the shape
attemptAutoLogingates on (packages/core/src/contexts/auth-context.tsx), so the fix reaches consumers through the subscription already in place. A.d.tsdiff of 0.5.1 → 0.6.0 confirms the type surface is purely additive: one new export (getValidAccessTokenOrThrow),AuthClientunchanged.Transient failures are unaffected — a 5xx, a timeout, and a network failure all still leave the session intact.
Consumer-visible behaviour change
fetchandgetAuthHeadersno longer throwError("No valid access token")on a rejected refresh; they throw the underlying error. Apps that detected dead sessions by matching that message have silently stopped detecting them and should listen forlogout/auth_state_changedinstead. Called out in the changeset.Grepping the installed package for that string does not tell you whether you are affected — the throw survives for the genuinely-no-token case, so the grep passes either way.
Changes
packages/core/package.json—^0.5.1→^0.6.0(+ lockfile; includes a no-op pnpm reorder of@microsoft/api-extractor, same specifier and version)packages/core/src/contexts/auth-context.test.tsx— regression test that the 0.6.0 teardown sequence (logoutimmediately followed byauth_state_changed) yields exactly one login redirect, not twodocs/concepts/authentication.md— new "Session expiry" section; corrects three places that describedfetchas handling refresh but not rejection.changeset/— patch@tailor-platform/auth-public-clientis permanently exempt from the repo'sminimumReleaseAgegate (pnpm-workspace.yaml), so the 9-day-old release installs cleanly in CI.Testing
build,type-check,lint,fmt:checkpass; 1580 tests passNot verified here: the original deadlock cannot be reproduced in this repo — it needs a real expired or revoked refresh token against a live workspace, and app-shell has none. The browser check proves the dependency loads and integrates, not that the deadlock is gone. End-to-end confirmation has to come from a consuming app; Denim Tears is the natural candidate, and its app-side mitigation (
tailor-professional-service/denim-tears#1479) should become removable once this ships.Not done
getValidAccessTokenOrThrowis not re-exported. app-shell exposes no auth utils today, only theAuthClienttype — adding one is an API design decision rather than part of a dependency bump. Happy to add it if wanted.References
fix(deps))🤖 Generated with Claude Code