# Introduction (/docs)

Session kit is a React library for apps whose own backend signs users in and
issues access and refresh tokens. You hand it the tokens your backend
returns, and it manages the session from there. It works with TanStack
Router, React Router, Expo Router, TanStack Start, and Next.js.

This page explains why it exists, what it does, and the terms the rest of the
docs use. To start using it, see [Installation](/docs/installation).

## Why Session kit [#why-session-kit]

Signing in is the easy part. Your backend checks a password and answers with
an access token and a refresh token, and for a moment the app is done.

Then the user reloads the page. The access token expires halfway through a
form. They open a second tab, sign out of one, and switch accounts in the
other. The backend goes down for a minute, just as a refresh was due.

Every app with its own backend has to get these moments right. It usually
takes a few hundred lines spread across an auth context, a fetch wrapper, and
the route guards, and one or two cases stay missing until a user finds them.
Session kit is that code, written once and tested with the routers and
frameworks it supports.

### What it does [#what-it-does]

Session kit handles each of these moments for you:

| When…                                             | Session kit…                                                                                                                                    |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| The page reloads                                  | Restores the saved session and checks the user against your schema again. With `localStorage` and a synchronous schema, before the first paint. |
| The access token is about to expire               | Refreshes it early, in the background, so requests don't wait.                                                                                  |
| A request gets a 401 anyway                       | Refreshes once, however many requests failed, then replays the safe ones.                                                                       |
| Two tabs need a refresh at the same moment        | Lets one tab spend the refresh token and the other reuse what it saved, where the browser supports Web Locks.                                   |
| The user signs out or switches account in one tab | Follows in every tab, and remounts guarded pages so nothing of the previous account stays on screen.                                            |
| The backend rejects the refresh token             | Signs out in every tab and clears storage.                                                                                                      |
| The backend is down when a refresh is due         | Keeps the session, reports the error, and waits for a retry. An outage is not a sign-out.                                                       |
| A signed-out user opens a private page            | Sends them to sign-in, and back to where they were going afterwards.                                                                            |
| The page renders on the server                    | Reads the session from an encrypted cookie, so the first response is already signed in.                                                         |

### What it doesn't do [#what-it-doesnt-do]

Session kit doesn't sign users in. It has no sign-in form, no OAuth flow, and
no 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.

## How it works [#how-it-works]

Session kit sits between your backend and your components. Tokens travel on
the left, and only state reaches the right:

```text
    Your backend             Session kit             Your components
 ┌───────────────┐      ┌───────────────────┐      ┌──────────────────┐
 │ POST /login   │ ───▶ │ signIn()          │ ───▶ │ useAuth()        │
 │ POST /refresh │ ◀──▶ │ refresh, on time  │      │ route guards     │
 │ GET  /orders  │ ◀─── │ authFetch()       │ ◀─── │ loaders, queries │
 └───────────────┘      └───────────────────┘      └──────────────────┘
   issues tokens          keeps the session        state, never tokens
```

Your backend issues the tokens. Session kit saves them, refreshes them on
time, and attaches them to your requests. Your components and route guards
read the session's state, and never see a token.

Five terms come up throughout:

- **Tokens** are what your backend issues. The access token goes with each
  request and is short-lived. The refresh token, if there is one, is exchanged
  for a new access token when the old one expires.
- **The client**, from `createAuth`, holds the tokens and does the work:
  saving, restoring, refreshing, and telling other tabs. An app usually has
  one.
- **A session** is who is signed in: `{ sessionId, user }`. It never holds
  tokens, so it is safe to render and to pass around.
- **State** is what components read: `status`, `user`, `sessionId`, and
  `error`, updated as the session changes.
- **Credentials** are the access token and its expiry, given only to code that
  attaches them to requests, such as `createAuthFetch`.

`status` says where the session stands, and checking it narrows `user`:

| `status`          | Meaning                                                                   | `user` |
| ----------------- | ------------------------------------------------------------------------- | ------ |
| `initializing`    | The saved session is being restored.                                      | `null` |
| `authenticated`   | Access is usable.                                                         | Set    |
| `refreshing`      | A refresh is replacing expired or rejected access.                        | Set    |
| `locked`          | With `locking`, a session is saved, but its tokens wait for `unlock()`.   | Set    |
| `unauthenticated` | No session; sign-in is needed.                                            | `null` |
| `unavailable`     | Restoring failed, or a failed refresh left no usable access; see `error`. | `null` |

The session is kept in one of two places, depending on where your app renders:

|                   | In the browser or on the device                                                  | On the server                                              |
| ----------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| For               | Single-page apps and Expo apps                                                   | TanStack Start, Next.js, React Router with SSR             |
| Client            | `createAuth`                                                                     | `createServerAuth`, with `createMirrorAuth` in the browser |
| Saved in          | `localStorage`, `sessionStorage`, the iOS keychain, or Android encrypted storage | An encrypted `httpOnly` cookie                             |
| The refresh token | Stays in that storage                                                            | Never reaches the browser                                  |

Hooks, guards, and tab sync work the same in both.

## Next steps [#next-steps]

- [Installation](/docs/installation): install the package and find the guide
  for your setup.
- [Getting started](/docs/getting-started): build a small app that signs in,
  stays signed in, and calls an API.
- [Server rendering](/docs/server-rendering): keep the session on the server
  with TanStack Start, Next.js, or React Router.