Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Session middleware for Remix using signed cookies. It loads session state from incoming requests, stores it in request context using Session, and persists updates automatically.
Features
- Session Lifecycle Handling - Reads and saves session state per request
- Context Integration - Exposes session APIs directly on request context
- Secure Cookie Support - Designed for signed session cookies
Installation
Usage
import { createRouter } from 'remix/fetch-router'
import { createCookie } from 'remix/cookie'
import { Session } from 'remix/session'
import { createCookieSessionStorage } from 'remix/session/cookie-storage'
import { session } from 'remix/session-middleware'
let sessionCookie = createCookie('__session', {
secrets: ['s3cr3t'], // session cookies must be signed!
httpOnly: true,
secure: true,
sameSite: 'lax',
})
let sessionStorage = createCookieSessionStorage()
let router = createRouter({
middleware: [session(sessionCookie, sessionStorage)],
})
router.get('/', (context) => {
let session = context.get(Session)
session.set('count', Number(session.get('count') ?? 0) + 1)
return new Response(`Count: ${session.get('count')}`)
})
The middleware:
- Reads the session from the cookie on incoming requests
- Makes it available as
context.get(Session)
- Automatically saves session changes and sets the cookie on responses
The session cookie must be signed for security. This prevents tampering with the session data on the client.
Login/Logout Flow
A basic login/logout flow could look like this:
import * as res from 'remix/fetch-router/response-helpers'
import { Session } from 'remix/session'
import { html } from 'remix/html-template'
router.get('/login', ({ get }) => {
let session = get(Session)
let error = session.get('error')
return res.html(html`
<html>
<body>
<h1>Login</h1>
${error ? html`<div class="error">${error}</div>` : null}
<form method="POST" action="/login">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
</body>
</html>
`)
})
router.post('/login', ({ get }) => {
let session = get(Session)
let formData = get(FormData)
let username = formData.get('username')
let password = formData.get('password')
let user = authenticateUser(username, password)
if (!user) {
session.flash('error', 'Invalid username or password')
return res.redirect('/login')
}
// Regenerate session ID for security
session.regenerateId()
session.set('userId', user.id)
return res.redirect('/dashboard')
})
router.post('/logout', ({ get }) => {
let session = get(Session)
session.destroy()
return res.redirect('/')
})
Flash Messages
Flash messages are perfect for displaying one-time notifications after redirects:
router.post('/contact', async ({ get }) => {
let session = get(Session)
let formData = get(FormData)
try {
await sendContactEmail(formData)
session.flash('success', 'Message sent successfully!')
return res.redirect('/contact')
} catch (error) {
session.flash('error', 'Failed to send message')
return res.redirect('/contact')
}
})
router.get('/contact', ({ get }) => {
let session = get(Session)
let success = session.get('success')
let error = session.get('error')
return res.html(html`
<html>
<body>
${success ? html`<div class="success">${success}</div>` : null}
${error ? html`<div class="error">${error}</div>` : null}
<form method="POST">
<!-- form fields -->
</form>
</body>
</html>
`)
})
Protected Routes
Use session data to protect routes that require authentication:
function requireAuth(context: Context) {
let session = context.get(Session)
let userId = session.get('userId')
if (!userId) {
session.flash('error', 'Please log in to continue')
return res.redirect('/login')
}
return null // Allow request to proceed
}
router.get('/dashboard', (context) => {
let authResponse = requireAuth(context)
if (authResponse) return authResponse
let session = context.get(Session)
let userId = session.get('userId')
return res.html(html`
<html>
<body>
<h1>Dashboard</h1>
<p>Welcome, user ${userId}!</p>
</body>
</html>
`)
})
Storage Options
The middleware works with any session storage strategy:
Cookie Storage
import { createCookieSessionStorage } from 'remix/session/cookie-storage'
let sessionStorage = createCookieSessionStorage()
let router = createRouter({
middleware: [session(sessionCookie, sessionStorage)],
})
Filesystem Storage
import { createFsSessionStorage } from 'remix/session/fs-storage'
let sessionStorage = createFsSessionStorage('/tmp/sessions')
let router = createRouter({
middleware: [session(sessionCookie, sessionStorage)],
})
External Storage
import { createRedisSessionStorage } from '@remix-run/session-storage-redis'
let sessionStorage = createRedisSessionStorage({
url: 'redis://localhost:6379',
})
let router = createRouter({
middleware: [session(sessionCookie, sessionStorage)],
})
API Reference
session(cookie, storage)
Creates session middleware for fetch-router.
Parameters:
cookie: Cookie - A signed cookie instance for session identification
storage: SessionStorage - The session storage strategy to use
Returns: Middleware
Using Session in Routes
Access the session from route handlers using context.get(Session):
import { Session } from 'remix/session'
router.get('/profile', (context) => {
let session = context.get(Session)
// Read values
let userId = session.get('userId')
let theme = session.get('theme')
// Set values
session.set('lastVisit', Date.now())
// Flash messages
session.flash('notification', 'Profile updated')
// Check for values
if (session.has('userId')) {
// user is logged in
}
// Remove values
session.unset('tempData')
return Response.json({ userId, theme })
})
Security Best Practices
Always Sign Session Cookies
Session cookies must be signed to prevent tampering:
let sessionCookie = createCookie('__session', {
secrets: ['your-secret-key'], // Required!
httpOnly: true,
secure: true,
sameSite: 'lax',
})
Regenerate Session IDs on Login
Prevent session fixation attacks by regenerating the session ID after authentication:
router.post('/login', ({ get }) => {
let session = get(Session)
if (validCredentials) {
session.regenerateId() // Important!
session.set('userId', user.id)
return res.redirect('/dashboard')
}
})
Destroy Sessions on Logout
Properly clean up sessions when users log out:
router.post('/logout', ({ get }) => {
let session = get(Session)
session.destroy()
return res.redirect('/')
})
session - Session management and storage
cookie - Cookie parsing and serialization
fetch-router - Router for the web Fetch API