remix-run/remix · critical · Error

Session is not started. Use session() middleware before csrf

Error message

Session is not started. Use session() middleware before csrf().

What it means

getCsrfToken reads the CSRF token from the session attached to the request context and throws if context.get(Session) is null. This is the helper-level counterpart to the csrf() middleware ordering error: any manual call to getCsrfToken (e.g. to render a token into a form) requires session() middleware to have run first.

Source

Thrown at packages/csrf-middleware/src/lib/csrf.ts:181

    return next()
  }
}

function isSafeMethod(method: string, safeMethods: readonly RequestMethod[]): boolean {
  return isRequestMethod(method) && safeMethods.includes(method)
}

/**
 * Gets the CSRF token from the session. Creates one if missing.
 *
 * @param context Request context with a started session
 * @param tokenKey Session key that stores the token
 * @returns The active CSRF token
 */
export function getCsrfToken(context: AnyRequestContext, tokenKey = '_csrf'): string {
  let session = context.get(Session)
  if (session == null) {
    throw new Error('Session is not started. Use session() middleware before csrf().')
  }

  let token = session.get(tokenKey)
  if (typeof token === 'string' && token !== '') {
    return token
  }

  let createdToken = createCsrfToken()
  session.set(tokenKey, createdToken)

  return createdToken
}

function createCsrfToken(): string {
  let bytes = new Uint8Array(32)
  crypto.getRandomValues(bytes)

  let token = ''

View on GitHub (pinned to 9696913134)

Solutions

  1. Ensure session() middleware runs before any code calling getCsrfToken
  2. If calling manually outside the middleware chain, attach or create a Session on the context first
  3. In tests, construct a context with a Session instance before invoking the helper

Example fix

// before
export async function action({ context }: RouteArgs) {
  let token = getCsrfToken(context) // throws if session() not registered
}
// after
router.use(session())
export async function action({ context }: RouteArgs) {
  let token = getCsrfToken(context)
}
Defensive patterns

Strategy: type-guard

Validate before calling

import { Session } from 'remix'
if (context.get(Session) == null) {
  throw new Error('session() middleware must run before using getCsrfToken')
}

Type guard

import { Session } from 'remix'
function hasSession(context: Request['context']): boolean {
  return context.get(Session) != null
}

Try / catch

let token: string | undefined
try { token = getCsrfToken(context) } catch (e) { if (e instanceof Error && e.message.includes('session() middleware')) token = undefined; else throw e }

Prevention

When it happens

Trigger: Calling getCsrfToken(context) inside a component, action, or loader when session() middleware was not registered or ran after the calling code; also called from routes that bypass the middleware chain.

Common situations: Rendering a hidden '_csrf' input in a form template and calling the helper before sessions are set up; refactoring routes out from under the session middleware; unit tests invoking helpers with a bare context that has no Session attached.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/29b433cef55054c2. Report an issue: GitHub.