Your backend signs users in. Session kit handles everything after.
Hand it the tokens your sign-in endpoint returns. It saves, restores and refreshes the session, keeps tabs in step, and guards your routes.
export const auth = createAuth({ name: "my-app", user: userSchema, storage: webStorage(),});Before 09:00, your app signs in with your backend, then hands the tokens to Session kit. With the refresh option, the user stays signed in all hour: Session kit refreshes a minute before each expiry, saves the session at sign-in and on every refresh, reads it back on a reload at 09:20, where the route guard lets the page render, and keeps a second tab opened at 09:31 in step. Later, when the refresh token has expired too, the refresh fails: the session ends in every tab and the route guard redirects to the sign-in page. Without the refresh option, the session ends when access expires at 09:15, and the guard redirects at 09:16.
Illustrative: your app signs in with your backend, then from 09:00 Session kit runs the session, until it ends. Access tokens last 15 minutes. With refresh, the page reloads at 09:20, a second tab opens at 09:31, and later the refresh token expires too.
The whole idea in three files
A client that knows your backend, a route only signed-in users reach, and a page that reads the session. One TanStack Router app, type-checked against the published package. Each setup's guide has its own version.
Create the client
import { createAuth, webStorage } from "@monarcode/session-kit";
import { login, refresh, userSchema } from "./backend.js";
export const auth = createAuth({
// Keeps this app's saved session apart from any other on the same origin.
name: "my-app",
// Every user is checked against this schema, and typed by it.
user: userSchema,
// Where the session is saved: `localStorage`.
storage: webStorage(),
// How expired access is replaced.
refresh: ({ refreshToken, signal }) => refresh(refreshToken, signal),
});
/** Signs in with the backend, then hands the result to Session kit. */
export async function signInWithPassword(username: string, password: string) {
await auth.signIn(await login(username, password));
}
export type AppAuth = typeof auth;What it doesn't do
Session kit never signs users in. It has no sign-in form, OAuth flow or user database: it starts where your backend's sign-in response ends. If you need those too, use an auth service or framework such as Auth.js, Better Auth or Clerk, which manage their own sessions. Read the introduction
One client, wherever your app runs
Each setup has its own entry point, a guide and a runnable example app.
- TanStack Router
"@monarcode/session-kit/tanstack-router"Guards in beforeLoad, with the session in route context.example - TanStack Start
"@monarcode/session-kit/tanstack-start"Server rendering, with the session in an encrypted cookie.example - React Router
"@monarcode/session-kit/react-router"Declarative and data mode, and framework mode as a SPA.example - React Router SSR
"@monarcode/session-kit/react-router/server"Framework mode with server rendering, the session on the server.example - Next.js
"@monarcode/session-kit/next"The App Router, with a Proxy that refreshes ahead of pages.example - Expo Router
"@monarcode/session-kit/expo"Secure storage, a splash screen held while restoring, and deep links.example - Effect
"@monarcode/session-kit/effect"The session as a service, and an effetch plugin.example
What you get
- Schema-driven users
- Any Standard Schema library, such as Zod, Valibot or ArkType, checks the user at sign-in, refresh, restore and in other tabs, and types it.
- Tokens kept out of state
- The auth state holds no tokens, so it's safe to render and to put in route context. Only the code that sends your requests reads them.
- Guards for every router
- TanStack Router, React Router in all three modes, Expo Router and Next.js send signed-out users to sign in, then back to the page they asked for.
- Server rendering
- With TanStack Start, Next.js or React Router, the session lives in an encrypted httpOnly cookie.
- Native apps
- The iOS keychain and Android Keystore, a splash screen held while restoring, and biometric locking.
- Made to be debugged
- Every failure Session kit reports is an AuthError with a code, and debug mode logs each step without printing a token.
Add it to your app
Releases are published from CI with npm provenance.
About 11 kB gzipped in a single-page app.
npm install @monarcode/session-kit