Typed, request-scoped auth session for SSR React apps
npm install tanstack-auth-session> Typed, request-scoped auth session for modern React SSR applications
tanstack-auth-session provides a real session layer for React applications that use
SSR, loaders, middleware, or server components.
It is framework-agnostic and works consistently across **TanStack Router, Remix, Next.js,
Express/Fastify, and plain Fetch-based SSR**.
---
In modern React apps:
- Cookies are not sessions
- JWT is not authorization
- SSR has no shared user state
- Global variables break in Node.js
- Auth logic is duplicated everywhere
Most projects re-implement:
- cookie parsing
- token verification
- user validation
- role & permission checks
- SSR → client hydration
This leads to bugs, security issues, and untyped user objects.
---
- Secure cookie-based authentication
- Request-scoped session (no globals, no memory leaks)
- Runtime validation with Zod
- Fully typed session.user
- Permissions with session.can()
- Request-level cache
- SSR → Client hydration
- Framework-agnostic core
---
``bash`
npm install tanstack-auth-session
- react >= 18@tanstack/react-router >= 1
- (only if you use the adapter)
---
A Session:
- is created per request
- lives only during that request
- is cached per request
- is safe in concurrent SSR
- works on server and client
---
`ts
import { createAuthSession } from 'tanstack-auth-session'
import { z } from 'zod'
export const auth = createAuthSession({
cookieName: 'auth',
schema: z.object({
id: z.string(),
role: z.enum(['user', 'admin']),
}),
verify: async (token) => {
if (token === 'admin-token') {
return { id: '1', role: 'admin' }
}
return null
},
permissions: {
admin: ['users.read', 'users.write'],
user: ['profile.read'],
},
})
`
---
`ts
session.isAuthenticated()
session.isGuest()
session.user // typed user or null
session.can('users.write')
`
---
Calling fromRequest multiple times during the same request
will not re-verify the token or re-fetch the user.
`ts
const s1 = await auth.fromRequest(request)
const s2 = await auth.fromRequest(request)
// same instance
`
---
`ts
import { withAuthGuard } from 'tanstack-auth-session/adapters/tanstack-router'
export const beforeLoad = withAuthGuard(auth)
`
`ts
beforeLoad: async ({ context }) => {
const session = await auth.fromRequest(context.request)
if (session.isGuest()) {
throw redirect({ to: '/login' })
}
return { session }
}
`
---
`ts
export const loader = async ({ request }) => {
const session = await auth.fromRequest(request)
if (session.isGuest()) {
throw redirect('/login')
}
return json({ user: session.user })
}
`
---
`ts
import { cookies } from 'next/headers'
export async function getSession() {
const cookieStore = cookies()
const request = new Request('http://localhost', {
headers: {
cookie: cookieStore.toString(),
},
})
return auth.fromRequest(request)
}
`
---
`ts
app.use(async (req, res, next) => {
const request = new Request('http://localhost', {
headers: {
cookie: req.headers.cookie ?? '',
},
})
req.session = await auth.fromRequest(request)
next()
})
`
---
`tsx
import { AuthSessionProvider } from 'tanstack-auth-session'
`
`tsx
import { useAuthSession } from 'tanstack-auth-session'
const session = useAuthSession()
if (session.can('users.write')) {
return
}
`
---
- Use httpOnly` cookies
- Always validate user with Zod
- Never store session globally
- Rotate tokens on logout (recommended)
---
- Senior / Staff Frontend Engineers
- Fullstack React developers
- SSR applications
- SaaS & B2B products
- Teams that care about architecture
---
MIT