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.