Session Middleware
Session middleware using signed cookies that loads session state from incoming requests, stores it in request context using Session, and persists updates automatically.
Installation
Function
session()
Creates middleware that manages request session state on request context.
Signature:
function session(sessionCookie: Cookie, sessionStorage: SessionStorage): Middleware
Parameters:
sessionCookie - The session cookie to use (must be signed)
sessionStorage - The storage backend for session data
Returns: The session middleware.
Throws: Error if the session cookie is not signed.
Basic 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')}`)
})
How It Works
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.
Session Methods
session.get(key)
Get a value from the session.
let userId = session.get('userId')
session.set(key, value)
Set a value in the session.
session.set('userId', user.id)
session.has(key)
Check if a key exists in the session.
if (session.has('userId')) {
// User is logged in
}
session.delete(key)
Remove a key from the session.
session.flash(key, value)
Set a value that will only be available on the next request.
session.flash('error', 'Invalid credentials')
session.regenerateId()
Regenerate the session ID (useful after login to prevent session fixation attacks).
session.destroy()
Destroy the session (useful for logout).
Login/Logout Flow
import * as res from 'remix/fetch-router/response-helpers'
import { Session } from 'remix/session'
import { formData } from 'remix/form-data-middleware'
let router = createRouter({
middleware: [session(sessionCookie, sessionStorage), formData()],
})
router.get('/login', ({ get }) => {
let session = get(Session)
let error = session.get('error')
return res.html(`
<html>
<body>
<h1>Login</h1>
${error ? `<div class="error">${error}</div>` : ''}
<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')
}
session.regenerateId()
session.set('userId', user.id)
return res.redirect('/dashboard')
})
router.post('/logout', ({ get }) => {
let session = get(Session)
session.destroy()
return res.redirect('/')
})
Authentication Guard
import { createRouter } from 'remix/fetch-router'
import { Session } from 'remix/session'
import * as res from 'remix/fetch-router/response-helpers'
let router = createRouter({
middleware: [session(sessionCookie, sessionStorage)],
})
// Middleware to require authentication
function requireAuth(context, next) {
let session = context.get(Session)
if (!session.has('userId')) {
session.flash('error', 'Please log in to continue')
return res.redirect('/login')
}
return next()
}
// Protected routes
router.get('/dashboard', requireAuth, (context) => {
let session = context.get(Session)
let userId = session.get('userId')
return res.html(`<h1>Dashboard for user ${userId}</h1>`)
})
router.get('/profile', requireAuth, (context) => {
let session = context.get(Session)
let userId = session.get('userId')
return res.html(`<h1>Profile for user ${userId}</h1>`)
})
Flash Messages
import { createRouter } from 'remix/fetch-router'
import { Session } from 'remix/session'
import * as res from 'remix/fetch-router/response-helpers'
router.post('/posts', ({ get }) => {
let session = get(Session)
let formData = get(FormData)
// Create post...
session.flash('success', 'Post created successfully!')
return res.redirect('/posts')
})
router.get('/posts', ({ get }) => {
let session = get(Session)
let success = session.get('success') // Available once, then cleared
return res.html(`
<html>
<body>
${success ? `<div class="success">${success}</div>` : ''}
<h1>Posts</h1>
</body>
</html>
`)
})
Custom Session Storage
You can use different session storage backends:
import { createCookieSessionStorage } from 'remix/session/cookie-storage'
import { createMemorySessionStorage } from 'remix/session/memory-storage'
import { createRedisSessionStorage } from 'remix/session/redis-storage'
import { createMemcacheSessionStorage } from 'remix/session/memcache-storage'
// Cookie storage (stores everything in the cookie)
let cookieStorage = createCookieSessionStorage()
// Memory storage (for development)
let memoryStorage = createMemorySessionStorage()
// Redis storage (for production)
let redisStorage = createRedisSessionStorage({
host: 'localhost',
port: 6379,
})
// Memcache storage (for production)
let memcacheStorage = createMemcacheSessionStorage({
hosts: ['localhost:11211'],
})
- fetch-router - Router for the web Fetch API
- session - Session management and storage
- cookie - Cookie parsing and serialization