Skip to content

Release 0.1.0 alpha.4 - #5

Merged
monarcode merged 12 commits into
mainfrom
0.1.0-alpha.4
Oct 5, 2026
Merged

monarcode merged 12 commits into
mainfrom
0.1.0-alpha.4

Conversation

@monarcode

@monarcode monarcode commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

This branch is the 0.1.0-alpha.4 release, based on 0.1.0-alpha.3. It has three themes:

  • Storage and users. Storage becomes an adapter, and users can come from the access token.
  • Router support. Routers get their own entry points, and the /react hooks no longer depend on TanStack Router.
  • Correctness. A security fix for return paths, plus several race-condition fixes.

It includes breaking changes (listed below), and the [CHANGELOG](CHANGELOG.md) has the full notes.

Security

  • safeReturnTo no longer returns paths whose dot segments resolve to another origin. Inputs like /.//evil.com and /%2e//evil.com used to become //evil.com, which browsers follow to evil.com. Apps on alpha.3 or earlier that redirect to safeReturnTo's result should upgrade.

What's new

Storage

  • Add a storage option and AuthStorage type, with webStorage() (localStorage, or sessionStorage per tab) and memoryStorage().
  • Apply storage operations one at a time in issue order, and re-check the session after every read. Late or out-of-order async storage can no longer erase a newer sign-in.
  • Make webStorage() explain when browser storage is missing, as on a server, instead of throwing a bare TypeError.

Users and sessions

  • Add fromAccessToken(claims, map?), which reads the user from a JWT access token.
  • Add auth.refresh() to force a refresh, and an optional revoke callback with the new REVOKE_FAILED code.
  • Make AuthState a discriminated union, so checking status narrows user.
  • Restore the saved session at mount when storage and the user source are synchronous, and mount AuthProvider in a layout effect so initializing never paints.
  • Drop the crypto.randomUUID and structuredClone requirements, for React Native.

React and routers

  • Add /tanstack-router with connectAuth, requireSession, SessionOutlet, redirectIfSignedIn and useRouterAuth.
  • Add /react-router for React Router 7 and 8 in declarative, data and framework (ssr: false) modes, with useAuthRevalidation and a basename option on requireSession.
  • Support TanStack Start in SPA mode: connectAuth is a no-op while the Router renders on a server.
  • Support React 18.

Docs and examples

  • Rewrite the README around a runnable DummyJSON quick start.
  • Add five example apps under examples/, built in CI. The repo is now a pnpm workspace.

Fixes

  • Unmounting no longer cancels a restore or refresh in progress. StrictMode used to flash unavailable, reject waiting loaders and send the same refresh token twice, which signed users out when the backend rotates tokens.
  • A failed signIn no longer cancels a restore or aborts a refresh. Only a newer signIn or signOut() cancels one still validating.
  • Mounting again applies what another tab saved meanwhile, such as a sign-out.
  • A React Router data-mode SessionOutlet no longer renders child routes with the previous account's loader data.
  • Guards read the session again when getSession() rejects with SESSION_CHANGED.

Breaking changes

  • createAuth takes user instead of userSchema, and storage is now required. Pass storage: webStorage() to keep the old behaviour.
  • getSession() and retry() return { sessionId, user } with no tokens.
    • Tokens move to auth.credentials.get().
    • refresh(session) becomes credentials.renew(credentials).
    • rejectSession(session) becomes credentials.reject(credentials).
    • auth.refresh() still exists but now takes no argument and forces a refresh.
  • createRefreshFn is removed. AuthClient is now AuthClient<User, Input> and needs both type arguments.
  • /react exports only createAuthHooks. connectAuth moves to /tanstack-router, and safeReturnTo moves to the package root.
  • The peer dependencies are optional and react-dom is no longer one. @tanstack/react-router now needs ^1.132.0.
  • Sign-in, updateUser and refresh reject with USER_VALIDATION_FAILED when the user schema does not accept its own output unchanged.
  • Sessions are saved in a new format, so users sign in once after upgrading.

Test plan

  • pnpm run check passes (lint, format, build, tests, type tests, README examples)
  • pnpm test passes in both the sync and test:async storage runs
  • pnpm run build:examples builds the package and all five example apps
  • CI's oldest-peers job passes (React 18.0.0, @tanstack/react-router 1.132.0, react-router 7.0.0)

- Add a `storage` option and `AuthStorage` type, with `webStorage()`
  (`localStorage` default, or `sessionStorage` per tab) and `memoryStorage()`
- Apply storage operations serially in issue order and re-check the session
  after each read, so late or out-of-order async storage cannot erase a newer
  sign-in
- Add storage tests and a `test:async` run that defers every storage operation
credentials

- Add `fromAccessToken`, `auth.refresh()` and a `revoke` callback with the
  `REVOKE_FAILED` error code, and make `AuthState` a discriminated union on
  `status`
- Breaking: `userSchema` becomes `user`, `storage` is required, and `AuthClient`
  takes `<User, Input>`. `createRefreshFn` is removed, and `getSession()`
  returns no tokens, which move to `auth.credentials` (`get`, `renew`, `reject`)
- Breaking: sessions are saved in a new format, so users sign in once after
  upgrading
…s from

Router

- Add `/tanstack-router` with `connectAuth`, `requireSession`, `SessionOutlet`,
  `redirectIfSignedIn` and `useRouterAuth`, and support React 18
- Breaking: `/react` exports only `createAuthHooks` (`useAuth`, `useAuthClient`,
  `AuthProvider`), `connectAuth` moves to `/tanstack-router`, and `safeReturnTo`
  moves to the package root
- Breaking: make both peer dependencies optional and drop the `react-dom` peer
… on mount

- Add `/react-router` with `SessionOutlet`, `requireSession`,
  `redirectIfSignedIn` and `useAuthRevalidation` for React Router 7 and 8, with
  `react-router` as an optional peer, and share guard logic with the TanStack
  Router entry point
- Restore the saved session at mount when storage and the user source answer
  synchronously, and mount `AuthProvider` in a layout effect so `initializing`
  never paints
Start renders its SPA shell on the server at build time. connectAuth mounted
the client there, found no storage, and published PERSISTENCE_FAILED, which
could end up in the shell HTML and break hydration.

- connectAuth returns a no-op disconnect when router.isServer is set, so the
  shell keeps the initializing state and renders the pending UI
- webStorage() explains when localStorage or sessionStorage does not exist,
  pointing to defaultSsr: false in Start, instead of a bare TypeError
- README: replace the "TanStack Start is not implemented" note; Start works in
  SPA mode with defaultSsr: false, React Router framework mode with ssr: false
- Add CHANGELOG entries and tests for both behaviors
- Add five apps under `examples/` (TanStack Router, TanStack Start SPA, and
  React Router in declarative, data and framework `ssr: false` modes). They
  share `/login` and a protected `/` page, sign in against DummyJSON, and read
  the user from the login response or the access token
- Turn the repo into a pnpm workspace over `examples/*`, add `build:examples`
  and a CI job that builds every app, and pass `--workspace-root` to the
  oldest-peers install. Bump the `@tanstack/react-router` dev dependency to
  1.170.41
- Document the apps in the README, `examples/README.md` and the CHANGELOG, and
  ignore generated build output in `.gitignore`
- Replace the quick start with a TanStack Router walkthrough that signs in
  against DummyJSON, and link the matching example app for each other supported
  setup
- Reorganize the reference into "Using the client" and "Schemas, refresh, and
  storage", with a peer dependency table, and drop the migration section
- Update the version line to `0.1.0-alpha.4` and add a CHANGELOG entry
safeReturnTo turned "/.//evil.com" and "/%2e//evil.com" into "//evil.com".
Check the parsed path, not the raw input. Bump to 0.1.0-alpha.4.
…d sign-ins

- Stop cancelling a restore or refresh in progress on unmount, so a StrictMode
  remount joins it. This fixes expired sessions flashing `unavailable`, loaders
  rejecting with `SESSION_CHANGED`, and a rotated refresh token being sent twice
- Track sign-in cancellation separately, so a failed `signIn` no longer aborts a
  restore or refresh, and only a newer `signIn` or `signOut()` cancels one still
  validating
- Make route guards read the session again when `getSession()` rejects with
  `SESSION_CHANGED`
- Move the deferred-storage test helpers into `tests/deferred-storage.ts`, and
  add regression tests for StrictMode, sign-in cancellation and guards
…rt basename

In data and framework mode, SessionOutlet shows pending until the loader
accepts a new account, so child routes never see the previous account's
loader data. requireSession takes the router's basename and leaves it out
of redirectTo.

BREAKING CHANGE: @tanstack/react-router ^1.132.0 is required; earlier
versions include the basepath in location.href.
…rces

Storage reads, user validation, and token claims each have one
implementation that finishes synchronously when every step does, so the
synchronous restore can no longer drift from the asynchronous one. Claims
that are not JSON now restore synchronously, async sources validate once,
and a remount applies changes other tabs saved while unmounted.
…ation

AuthClient requires both type arguments, so leaving out the input no longer
drops updateUser silently. Guard, React, and outlet client shapes are picked
from AuthClient. connectAuth and useAuthRevalidation share one version
watcher, which no longer revalidates after unmount. Regroup the alpha.4
changelog and document sessionStorage in duplicated tabs.
@monarcode
monarcode merged commit eb0c3db into main Oct 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant