Skip to content

auth-middleware Overview

The remix/auth-middleware package determines who the current user is on every request. It reads authentication credentials from the request (a session cookie, a bearer token, an API key) and resolves them into a user identity. It does not block requests on its own -- it only attaches identity information to the request context. To actually block unauthenticated users, you use requireAuth.

Key Concepts

  • Auth middleware -- Middleware that runs on every request and tries to determine the current user. It sets the Auth context key that downstream handlers can read.
  • Auth scheme -- A strategy for extracting and verifying credentials from a request. Examples: reading a user ID from a session cookie, validating a bearer token from the Authorization header, checking an API key from a custom header.
  • Auth state -- The result of authentication. Either { ok: true, identity, method } (user was identified) or { ok: false } (no valid credentials found).
  • requireAuth -- Middleware that checks the auth state and blocks the request if no user was identified.

How It Works

The auth middleware accepts an array of auth schemes and tries them in order. The first scheme that returns a valid identity wins. If no scheme succeeds, the auth state is set to { ok: false }.

Request ──> [Try Session Scheme] ──> [Try Bearer Scheme] ──> [Try API Key Scheme] ──> Auth State
ts
import { auth, createSessionAuthScheme, Auth } from 'remix/auth-middleware'

let sessionScheme = createSessionAuthScheme({
  name: 'session',
  read(session) {
    return session.get('userId') ?? null
  },
  async verify(userId) {
    let user = await db.findById(users, userId)
    if (!user) return null
    return { id: user.id, email: user.email, name: user.name, role: user.role }
  },
})

let authMiddleware = auth({ schemes: [sessionScheme] })

Handlers read the result from the Auth context key:

ts
import { Auth } from 'remix/auth-middleware'

router.get(homeRoute, async ({ context }) => {
  let authState = context.get(Auth)

  if (authState.ok) {
    return new Response(`Hello, ${authState.identity.name}!`)
  }

  return new Response('Hello, guest!')
})

Built-in Auth Schemes

SchemeFactoryReads From
SessioncreateSessionAuthSchemeSession cookie (via remix/session)
Bearer TokencreateBearerTokenAuthSchemeAuthorization: Bearer <token> header
API KeycreateAPIAuthSchemeCustom header (e.g. X-API-Key)

You can also write your own auth scheme by implementing the AuthScheme interface (an object with a name string and an authenticate method).

Protecting Routes with requireAuth

The requireAuth middleware blocks requests where no auth scheme succeeded. Place it on routes or groups of routes that need authentication.

ts
import { requireAuth, Auth } from 'remix/auth-middleware'

let requireLogin = requireAuth({
  onUnauthenticated(request) {
    let url = new URL(request.url)
    return new Response(null, {
      status: 302,
      headers: {
        Location: `/login?returnTo=${encodeURIComponent(url.pathname)}`,
      },
    })
  },
})

// This route is protected -- unauthenticated users are redirected to /login
router.get(profileRoute, { middleware: [requireLogin] }, async ({ context }) => {
  let { identity } = context.get(Auth)
  return new Response(`Welcome, ${identity.name}!`)
})

If you omit the onUnauthenticated callback, requireAuth returns a plain 401 Unauthorized response. This is useful for API endpoints.

Quick Example

A complete setup with session auth for browser users and bearer token auth for API clients:

ts
import { createRouter } from 'remix/fetch-router'
import { session } from 'remix/session-middleware'
import {
  auth,
  requireAuth,
  createSessionAuthScheme,
  createBearerTokenAuthScheme,
  Auth,
} from 'remix/auth-middleware'

// Define schemes
let sessionScheme = createSessionAuthScheme({
  name: 'session',
  read(session) { return session.get('userId') ?? null },
  async verify(userId) {
    let user = await db.findById(users, userId)
    return user ? { id: user.id, name: user.name, role: user.role } : null
  },
})

let bearerScheme = createBearerTokenAuthScheme({
  name: 'bearer',
  async verify(token) {
    let record = await db.findByToken(apiTokens, token)
    if (!record) return null
    let user = await db.findById(users, record.userId)
    return user ? { id: user.id, name: user.name, role: 'api' } : null
  },
})

// Create middleware
let authMiddleware = auth({ schemes: [sessionScheme, bearerScheme] })
let requireLogin = requireAuth()

// Set up router
let router = createRouter({
  middleware: [
    session(sessionCookie, sessionStorage),
    authMiddleware,
  ],
})

// Public route
router.get(homeRoute, async ({ context }) => {
  let authState = context.get(Auth)
  if (authState.ok) {
    return new Response(`Hello, ${authState.identity.name}!`)
  }
  return new Response('Hello, guest!')
})

// Protected route
router.get(dashboardRoute, { middleware: [requireLogin] }, async ({ context }) => {
  let { identity } = context.get(Auth)
  return Response.json({ user: identity })
})

How It Fits Together

  • remix/auth handles the initial identity verification (login). It produces the user identity that gets stored in the session.
  • remix/auth-middleware (this package) reads that stored identity back on subsequent requests and makes it available to handlers.
  • remix/session and remix/session-middleware provide the underlying session storage that the session auth scheme reads from.

Next Steps

Released under the MIT License.