Remix Auth
remix-auth is a TypeScript-first, strategy-based authentication library designed for Remix and React Router applications. Inspired by Passport.js, it provides full server-side authentication capabilities built on the Web Fetch API, ensuring compatibility with modern web standards. The library is currently stable at version 4.2.0 and maintains an active release cadence, frequently introducing new features, bug fixes, and documentation improvements. Key differentiators include its robust TypeScript support, extensible strategy pattern (with separate npm packages for various authentication flows like Form, OAuth2, etc.), and seamless integration with Remix's server-side action and loader functions, allowing developers to implement custom authentication logic. It dropped direct React Router requirements in v4.0.0, simplifying its core and making it more adaptable.
Common errors
-
Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/remix-auth/dist/index.js from .../app/services/auth.server.ts not supported. Instead change the require of index.js in .../app/services/auth.server.ts to a dynamic import() which is also all of CJS module.
cause `remix-auth` is an ES Module (ESM) but your project or a specific file is attempting to import it using CommonJS `require()` syntax.fixUpdate all `remix-auth` imports (and other ESM packages) in your project to use ES module syntax: `import { Authenticator } from 'remix-auth';`. Ensure your `tsconfig.json` and `package.json` are configured for ESM output if necessary. -
TypeError: authenticator.authenticate is not a function
cause The `authenticator` instance was not properly initialized or exported, or the `authenticate` method is being called on a non-`Authenticator` object.fixVerify that you have correctly instantiated `Authenticator` with `new Authenticator(sessionStorage)` and that the instance is correctly exported and imported where `authenticate` is called. Double-check for typos. -
Error: No strategy named "my-strategy" was found.
cause The `authenticator.authenticate()` method was called with a strategy name that has not been registered with the `authenticator.use()` method.fixEnsure that you have called `authenticator.use(new MyStrategy(...), 'my-strategy')` to register your strategy with the correct name before attempting to authenticate with it. -
TypeError: Cannot read properties of undefined (reading 'headers')
cause The `request` object passed to `authenticator.authenticate()` or `authenticator.isAuthenticated()` is `undefined` or `null`.fixEnsure that the `request` object from your Remix `loader` or `action` function is correctly passed as the second argument to `authenticator.authenticate()` or `authenticator.isAuthenticated()`.
Warnings
- breaking Version 4.0.0 introduced significant breaking changes, including dropping direct React Router requirements and a modernization of the package setup. This likely involves updates to how `Authenticator` and strategies are initialized and configured.
- breaking As of v4.0.0, `remix-auth` is distributed as an ESM-only package. This means CommonJS `require()` syntax will no longer work and must be replaced with ESM `import` statements.
- gotcha Authentication strategies (e.g., `remix-auth-form`, `remix-auth-oauth2`) are separate npm packages. It is crucial to verify that the version of any strategy you install is compatible with your `remix-auth` version, as they may not be updated simultaneously.
- breaking Remix v2 support was explicitly added in `remix-auth` v3.6.0. Earlier versions of `remix-auth` may not be fully compatible with Remix v2, leading to unexpected behavior or errors.
- gotcha The `Authenticator` requires a `SessionStorage` instance (e.g., `createCookieSessionStorage`, `createMemorySessionStorage`) for managing user sessions. Misconfiguration or failure to provide a valid `SessionStorage` can lead to authentication failures or state loss.
Install
-
npm install remix-auth -
yarn add remix-auth -
pnpm add remix-auth
Imports
- Authenticator
const { Authenticator } = require('remix-auth');import { Authenticator } from 'remix-auth'; - FormStrategy
import { FormStrategy } from 'remix-auth-form'; - ActionArgs
import type { ActionFunction } from '@remix-run/node';import type { ActionArgs } from '@remix-run/node';
Quickstart
import { Form } from '@remix-run/react';
import { Authenticator } from 'remix-auth';
import { FormStrategy } from 'remix-auth-form';
import { createCookieSessionStorage, redirect } from '@remix-run/node';
import type { ActionArgs, LoaderArgs } from '@remix-run/node';
// 1. Define your User type
interface User { id: string; email: string; name: string; }
// 2. Setup session storage
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
path: '/',
sameSite: 'lax',
secrets: [process.env.SESSION_SECRET ?? 's3cr3t'], // Use an environment variable for secrets
secure: process.env.NODE_ENV === 'production',
},
});
// 3. Create Authenticator instance
export let authenticator = new Authenticator<User>(sessionStorage);
// 4. Register a strategy (e.g., FormStrategy)
authenticator.use(
new FormStrategy(async ({ form }) => {
let email = form.get('email') as string;
let password = form.get('password') as string;
// Simulate user login
if (email === 'test@example.com' && password === 'password') {
return { id: '123', email, name: 'Test User' };
}
throw new Error('Invalid credentials');
}),
'user-pass' // Name of the strategy
);
// 5. Create a Login route (app/routes/login.tsx)
export default function Screen() {
return (
<Form method='post'>
<input type='email' name='email' required placeholder='Email' />
<input
type='password'
name='password'
autoComplete='current-password'
required
placeholder='Password'
/>
<button type='submit'>Sign In</button>
</Form>
);
}
// 6. Handle authentication in the action function
export async function action({ request }: ActionArgs) {
try {
let user = await authenticator.authenticate('user-pass', request, {
successRedirect: '/',
failureRedirect: '/login?error=true',
});
return user; // Should not reach here if redirects work
} catch (error) {
// This catch block handles redirect responses thrown by authenticate
// or other errors. Remix will re-throw the response.
throw error;
}
}
// 7. Example loader to check authentication
export async function loader({ request }: LoaderArgs) {
let user = await authenticator.isAuthenticated(request);
if (!user) {
throw redirect('/login');
}
return { user };
}