• Stars
    star
    1,782
  • Rank 26,107 (Top 0.6 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 3 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple Authentication for Remix

Remix Auth

Simple Authentication for Remix

Features

  • Full Server-Side Authentication
  • Complete TypeScript Support
  • Strategy-based Authentication
  • Easily handle success and failure
  • Implement custom strategies
  • Supports persistent sessions

Overview

Remix Auth is a complete open-source authentication solution for Remix.run applications.

Heavily inspired by Passport.js, but completely rewrote it from scratch to work on top of the Web Fetch API. Remix Auth can be dropped in to any Remix-based application with minimal setup.

As with Passport.js, it uses the strategy pattern to support the different authentication flows. Each strategy is published individually as a separate npm package.

Installation

To use it, install it from npm (or yarn):

npm install remix-auth

Also, install one of the strategies. A list of strategies is available in the Community Strategies discussion.

Usage

Remix Auth needs a session storage object to store the user session. It can be any object that implements the SessionStorage interface from Remix.

In this example I'm using the createCookieSessionStorage function.

// app/services/session.server.ts
import { createCookieSessionStorage } from "@remix-run/node";

// export the whole sessionStorage object
export let sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "_session", // use any name you want here
    sameSite: "lax", // this helps with CSRF
    path: "/", // remember to add this so the cookie will work in all routes
    httpOnly: true, // for security reasons, make this cookie http only
    secrets: ["s3cr3t"], // replace this with an actual secret
    secure: process.env.NODE_ENV === "production", // enable this in prod only
  },
});

// you can also export the methods individually for your own usage
export let { getSession, commitSession, destroySession } = sessionStorage;

Now, create a file for the Remix Auth configuration. Here import the Authenticator class and your sessionStorage object.

// app/services/auth.server.ts
import { Authenticator } from "remix-auth";
import { sessionStorage } from "~/services/session.server";

// Create an instance of the authenticator, pass a generic with what
// strategies will return and will store in the session
export let authenticator = new Authenticator<User>(sessionStorage);

The User type is whatever you will store in the session storage to identify the authenticated user. It can be the complete user data or a string with a token. It is completely configurable.

After that, register the strategies. In this example, we will use the FormStrategy to check the documentation of the strategy you want to use to see any configuration you may need.

import { FormStrategy } from "remix-auth-form";

// Tell the Authenticator to use the form strategy
authenticator.use(
  new FormStrategy(async ({ form }) => {
    let email = form.get("email");
    let password = form.get("password");
    let user = await login(email, password);
    // the type of this user must match the type you pass to the Authenticator
    // the strategy will automatically inherit the type if you instantiate
    // directly inside the `use` method
    return user;
  }),
  // each strategy has a name and can be changed to use another one
  // same strategy multiple times, especially useful for the OAuth2 strategy.
  "user-pass"
);

Now that at least one strategy is registered, it is time to set up the routes.

First, create a /login page. Here we will render a form to get the email and password of the user and use Remix Auth to authenticate the user.

// app/routes/login.tsx
import type { ActionArgs, LoaderArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { Form } from "@remix-run/react";
import { authenticator } from "~/services/auth.server";

// First we create our UI with the form doing a POST and the inputs with the
// names we are going to use in the strategy
export default function Screen() {
  return (
    <Form method="post">
      <input type="email" name="email" required />
      <input
        type="password"
        name="password"
        autoComplete="current-password"
        required
      />
      <button>Sign In</button>
    </Form>
  );
}

// Second, we need to export an action function, here we will use the
// `authenticator.authenticate method`
export async function action({ request }: ActionArgs) {
  // we call the method with the name of the strategy we want to use and the
  // request object, optionally we pass an object with the URLs we want the user
  // to be redirected to after a success or a failure
  return await authenticator.authenticate("user-pass", request, {
    successRedirect: "/dashboard",
    failureRedirect: "/login",
  });
};

// Finally, we can export a loader function where we check if the user is
// authenticated with `authenticator.isAuthenticated` and redirect to the
// dashboard if it is or return null if it's not
export async function loader({ request }: LoaderArgs) {
  // If the user is already authenticated redirect to /dashboard directly
  return await authenticator.isAuthenticated(request, {
    successRedirect: "/dashboard",
  });
};

With this, we have our login page. If we need to get the user data in another route of the application, we can use the authenticator.isAuthenticated method passing the request this way:

// get the user data or redirect to /login if it failed
let user = await authenticator.isAuthenticated(request, {
  failureRedirect: "/login",
});

// if the user is authenticated, redirect to /dashboard
await authenticator.isAuthenticated(request, {
  successRedirect: "/dashboard",
});

// get the user or null, and do different things in your loader/action based on
// the result
let user = await authenticator.isAuthenticated(request);
if (user) {
  // here the user is authenticated
} else {
  // here the user is not authenticated
}

Once the user is ready to leave the application, we can call the logout method inside an action.

export async function action({ request }: ActionArgs) {
  await authenticator.logout(request, { redirectTo: "/login" });
};

Advanced Usage

Custom redirect URL based on the user

Say we have /dashboard and /onboarding routes, and after the user authenticates, you need to check some value in their data to know if they are onboarded or not.

If we do not pass the successRedirect option to the authenticator.authenticate method, it will return the user data.

Note that we will need to store the user data in the session this way. To ensure we use the correct session key, the authenticator has a sessionKey property.

export async function action({ request }: ActionArgs) {
  let user = await authenticator.authenticate("user-pass", request, {
    failureRedirect: "/login",
  });

  // manually get the session
  let session = await getSession(request.headers.get("cookie"));
  // and store the user data
  session.set(authenticator.sessionKey, user);

  // commit the session
  let headers = new Headers({ "Set-Cookie": await commitSession(session) });

  // and do your validation to know where to redirect the user
  if (isOnboarded(user)) return redirect("/dashboard", { headers });
  return redirect("/onboarding", { headers });
};

Changing the session key

If we want to change the session key used by Remix Auth to store the user data, we can customize it when creating the Authenticator instance.

export let authenticator = new Authenticator<AccessToken>(sessionStorage, {
  sessionKey: "accessToken",
});

With this, both authenticate and isAuthenticated will use that key to read or write the user data (in this case, the access token).

If we need to read or write from the session manually, remember always to use the authenticator.sessionKey property. If we change the key in the Authenticator instance, we will not need to change it in the code.

Reading authentication errors

When the user cannot authenticate, the error will be set in the session using the authenticator.sessionErrorKey property.

We can customize the name of the key when creating the Authenticator instance.

export let authenticator = new Authenticator<User>(sessionStorage, {
  sessionErrorKey: "my-error-key",
});

Furthermore, we can read the error using that key after a failed authentication.

// in the loader of the login route
export async function loader({ request }: LoaderArgs) {
  await authenticator.isAuthenticated(request, {
    successRedirect: "/dashboard",
  });
  let session = await getSession(request.headers.get("cookie"));
  let error = session.get(authenticator.sessionErrorKey);
  return json({ error });
};

Remember always to use the authenticator.sessionErrorKey property. If we change the key in the Authenticator instance, we will not need to change it in the code.

Errors Handling

By default, any error in the authentication process will throw a Response object. If failureRedirect is specified, this will always be a redirect response with the error message on the sessionErrorKey.

If a failureRedirect is not defined, Remix Auth will throw a 401 Unauthorized response with a JSON body containing the error message. This way, we can use the CatchBoundary component of the route to render any error message.

If we want to get an error object inside the action instead of throwing a Response, we can configure the throwOnError option to true. We can do this when instantiating the Authenticator or calling authenticate.

If we do it in the Authenticator, it will be the default behavior for all the authenticate calls.

export let authenticator = new Authenticator<User>(sessionStorage, {
  throwOnError: true,
});

Alternatively, we can do it on the action itself.

import { AuthorizationError } from "remix-auth";

export async function action({ request }: ActionArgs) {
  try {
    return await authenticator.authenticate("user-pass", request, {
      successRedirect: "/dashboard",
      throwOnError: true,
    });
  } catch (error) {
    // Because redirects work by throwing a Response, you need to check if the
    // caught error is a response and return it or throw it again
    if (error instanceof Response) return error;
    if (error instanceof AuthorizationError) {
      // here the error is related to the authentication process
    }
    // here the error is a generic error that another reason may throw
  }
};

If we define both failureRedirect and throwOnError, the redirect will happen instead of throwing an error.

More Repositories

1

remix-utils

A set of utility functions and types to use with Remix.run
TypeScript
1,934
star
2

remix-i18next

The easiest way to translate your Remix apps
TypeScript
520
star
3

impresionante-javascript

Recopilaci贸n de links en espa帽ol sobre JavaScript
415
star
4

flagged

Feature Flags for React made easy with hooks, HOC and Render Props
TypeScript
386
star
5

remix-hono

Hono middlewares for Remix
TypeScript
321
star
6

next-ga

Next.js HOC to integrate Google Analytics on every page change
JavaScript
232
star
7

grial

A Node.js framework for creating GraphQL API servers easily and without a lot of boilerplate.
JavaScript
189
star
8

next-nprogress

Next.js HOC to integrate NProgress inside your app
JavaScript
149
star
9

remix-auth-oauth2

A OAuth2Strategy for Remix Auth
TypeScript
129
star
10

use-mutation

馃К Run side-effects safely in React
TypeScript
119
star
11

swr-sync-storage

Synchronize SWR cache with localStorage or sessionStorage to get offline cache
TypeScript
109
star
12

react-lazy-image

Component to render images and lazyload them if are in the viewport (or near to them).
JavaScript
101
star
13

personal-site

Personal website
TypeScript
95
star
14

now-storage

Use Now static deployments to upload and store files.
JavaScript
90
star
15

remix-auth-form

A Remix Auth strategy for working with forms.
TypeScript
79
star
16

sergiodxa.com

The code behind sergiodxa.com
TypeScript
71
star
17

remix-auth-github

A GitHubStrategy for Remix Auth, based on the OAuth2Strategy
TypeScript
66
star
18

redux-in-spanish

Traducci贸n al espa帽ol de la documentaci贸n de Redux.
64
star
19

web-oidc

An OpenID Connect client built using only Web APIs
TypeScript
49
star
20

remix-auth-strategy-template

A template for creating a new Remix Auth strategy.
TypeScript
47
star
21

remix-socket.io

A Remix app using Express and Socket.io
TypeScript
45
star
22

next-socket.io

An example app with Next.js and socket.io
JavaScript
38
star
23

remix-inspector

Remix devtools to inspect your route data.
TypeScript
38
star
24

react-wordpress

Example of a React+Next.js+WordPress application.
JavaScript
36
star
25

react-lazy-memo

CRA with Suspense, lazy and memo usage demo app
JavaScript
36
star
26

swr-mutate-many

Little function to call mutate against multiple cached keys of SWR
TypeScript
26
star
27

remix-demo-infinite-scroll

A collection of infinite scroll pagination demos built with Remix
TypeScript
26
star
28

now-ab

A simple proxy server to handle Now.sh deployments AB tests
JavaScript
25
star
29

remix-on-bun

TypeScript
25
star
30

es6

Material para la clase BONUS en Comunidad Platzi sobre ECMAScript 6.
HTML
23
star
31

react-course-project

Proyecto para el curso de React.js
JavaScript
22
star
32

micro-next

Integrations between Micro and Next.js
JavaScript
20
star
33

collected-notes

A TypeScript client for the Collected Notes API
17
star
34

now-parcel

A Now v2 Parcel builder.
JavaScript
16
star
35

api-client

A strongly typed API client using Zod
TypeScript
15
star
36

remix-demo-file-upload

A simple demo on how to add a file upload that shows the image being uploaded and later replace it with the actual one
TypeScript
15
star
37

remix-vite-i18next

An example Remix + Vite app with remix-i18next setup
TypeScript
14
star
38

collected-remix

A Collected Notes client in Remix
TypeScript
12
star
39

micro-platzi-profile

Ejemplo de microservicio usando https://github.com/zeit/micro para scrappear un perf铆l de Platzi
JavaScript
12
star
40

markdown-it-mentions

markdown-it plugin to support Twitter like mentions
JavaScript
11
star
41

react-use-permissions

React hook for Permissions API
JavaScript
11
star
42

use-safe-callback

Wrap a function to ensure you never call it if a component is unmounted, useful to avoid cases where an async code could finish after a component has unmounted and it tries to update a state
TypeScript
11
star
43

redux-duck

Helper function to create Redux modules using the ducks-modular-redux proposal
TypeScript
10
star
44

use-log

Log a state or prop every time it changes
TypeScript
10
star
45

cf-bootcamp-react-router-lesson

TypeScript
9
star
46

react-simple-modal

React modal make it simple.
JavaScript
8
star
47

collected-notes-website

A Next.js based clon of the Collected Notes website
TypeScript
8
star
48

remix-mdn

A MDN clone of Remix
TypeScript
8
star
49

remix-auth-token

Token based authentication for Remix Auth
TypeScript
8
star
50

next-analytics

Next.js HOC to integrate Google Analytics and FB Pixel
JavaScript
8
star
51

personal-slides

The slides of my talks
JavaScript
7
star
52

collected-notes-next-blog

Blog example using Collected Notes as CMS
TypeScript
7
star
53

react-use-quicklink

Quicklink hook for React
JavaScript
6
star
54

use-validate-image-url

TypeScript
6
star
55

email-value

TypeScript
6
star
56

isomorphic-react-todo

Aplicaci贸n web de TODOs isom贸rfica hecha con React.js
JavaScript
6
star
57

use-consistent-value

Keep a consistent reference to an object or array based on their values.
TypeScript
6
star
58

markdown-it-codesandbox

markdown-it plugin to embed CodeSandbox editor.
JavaScript
5
star
59

remix-demo-prefetch-fetcher

Demo Remix app to prefetch the fetcher data
TypeScript
5
star
60

next-custom-query

Example Next.js app using a custom server with queries in the URLs.
JavaScript
5
star
61

yifi-search

Backbone.js WebApp for search torrents with the YIFI's API.
JavaScript
5
star
62

navBarAdaptable-mejorUX

Barra de navegaci贸n adaptable con Media Queries y JS
CSS
5
star
63

next-babel-minify

Next.js plugin to replace UglifyJS for BabelMinify
JavaScript
5
star
64

gulp-tasks-boilerplate

Plantilla de tareas de Gulp.js para distintos usos, con un package.json con todos los m贸dulos requeridos.
JavaScript
5
star
65

remix-auth-webauthn

A strategy to support WebAuthn
TypeScript
4
star
66

remix-auth-austin-demo

Remix Auth demo for the Remix Austin meetup
TypeScript
4
star
67

MarvelDB

Webapp to find characters in the Marvel database.
JavaScript
4
star
68

remix-markdoc-example

TypeScript
4
star
69

remix-define-routes

A DSL to define Remix routes with code
TypeScript
4
star
70

swr-sync-session

This is an example Next.js + SWR application using SWR revalidation on focus feature to sync session between tabs
JavaScript
4
star
71

dataset

A free to use API to get a lists of various common datasets such as languages, countries, etc.
JavaScript
3
star
72

next-credentials

Next.js example using Credential Management API
JavaScript
3
star
73

personal-cli

A CLI tool with commands for personal usage.
JavaScript
3
star
74

platzimusic

Proyecto para el diplomado de React en Platzi
JavaScript
3
star
75

cf-custom-hooks

Ejercicios de Custom Hooks para C贸digo Facilito
TypeScript
3
star
76

personal-api

An API created for my personal site usage.
JavaScript
2
star
77

cf-bootcamp-deploy

TypeScript
2
star
78

techtalks-remix-demo

Demo of Remix.run for Tech Talks.pe
TypeScript
2
star
79

react-render-service

React Render as a Service
JavaScript
1
star
80

personal-shortening

A personal URL shortening service
JavaScript
1
star
81

virtual-event-starter-kit

TypeScript
1
star
82

remix-with-sentry-sdk

A reproduction of the issue https://github.com/getsentry/sentry-javascript/issues/6294
TypeScript
1
star
83

archive-it

API to get the messages of a Slack channel
TypeScript
1
star
84

ngCreate

CLI to create AngularJS files
JavaScript
1
star
85

react-chat-app

JavaScript
1
star
86

pokedex-swr

Pokedex Application using Next.js and SWR with Tailwind for styling
TypeScript
1
star
87

swr-devtools

TypeScript
1
star
88

dice-roller

A tiny library to calculate roll dices using the classic D&D sintax: 1d20+4.
JavaScript
1
star
89

react-i18n

Ejemplo de internacionalizaci贸n con React.js y Format.js
JavaScript
1
star
90

es-query

An utilitarian library for DOM manipulation using ECMAScript 7 Function Bind Syntax
JavaScript
1
star
91

device-info

A little service to check a device information.
HTML
1
star
92

deno-md-to-html

Simple MD to HTML parser
TypeScript
1
star
93

package

A template to create new packages.
TypeScript
1
star
94

Check-userAgent

New method for JS window object that checks the userAgent and returns true if a mobile.
1
star
95

Elecalc

Webapp hecha con AngularJS para realizar distintos c谩lculos de electricidad de una manera f谩cil y r谩pida
HTML
1
star
96

tpb-app

WebApp in NodeJS to search torrents in The Pirate Bay
JavaScript
1
star
97

remix-island-demos

TypeScript
1
star