Skip to main content

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

npm i remix

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:
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

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