Telerik blogs

Are you struggling with implementing single sign-on (SSO) authentication in Remix? No worries, I’ll guide you through the process of implementing SSO using various OAuth providers and identity providers (IdPs).

I’ve written about SSO in Remix using Clerk and GitHub. In that article, we covered what SSO is and how it can be beneficial to you and your software users. The whole user identity was managed by Clerk, and this is not the case for every kind of software. You may want to manage your users yourself, but still use an OAuth provider (e.g., GitHub) to authenticate your users.

In this article, I’ll skip explaining SSO and go straight to showing you how to use GitHub for authentication, while managing your user data yourself.

Prerequisites

To follow along, you’ll need some familiarity with Remix (e.g., loaders, routes and actions) and an understanding of SSO.

Let’s get started!

Set up the Remix App

Create a new Remix app using npx create-remix. I’ll use JavaScript for this tutorial, but you’re free to create a TypeScript project.

After that’s done, run the following command to install the remix-auth packages.

npm install remix-auth remix-auth-github

Remix Auth is a strategy-based, server-side authentication solution for Remix applications. With TypeScript support, you can implement custom strategies, support persistent sessions and easily handle success and failure scenarios. There are several community-supported strategies for you to use. We’re going to make use of the GitHub Strategy in the preceding examples.

Create GitHub OAuth App

If you want to allow users to sign in using their GitHub profile, you’ll need to create a GitHub OAuth app. You can create and register a GitHub OAuth app under your personal account or under any organization you have administrative access to. Follow these instructions to create an OAuth app.

  1. In the upper-right corner of any GitHub page, click your profile photo, then click Settings.
  2. In the left sidebar, click <> Developer settings.
  3. In the left sidebar, click OAuth Apps.
  4. Click the New OAuth App button (or the Register a new application button).
  5. Enter a name for your app in the Application name input box.
  6. Type the full URL to your app’s website in the Homepage URL input box.
  7. Enter http://localhost:3000/auth/github/callback URL as the Authorization callback URL.
  8. Click the Register application button to complete the process.

You will be redirected to the general information page for your OAuth app. Copy the Client ID for later use. You also need a client secret. Click the Generate a new client secret button to generate one.

Generate client secret

Copy the secret to a text editor so that you can find it when you need it in the coming sections.

Authenticate Users Using Remix Auth

Remix Auth needs a session storage object to store the user’s session. It can be any object that implements the SessionStorage interface in Remix. Session storage understands how to parse and generate cookies, and how to store session data in a database or filesystem. Remix comes with several built-in session storage options for common scenarios, and one to create your own:

  • createCookieSessionStorage
  • createMemorySessionStorage
  • createFileSessionStorage (node)
  • createWorkersKVSessionStorage (Cloudflare Workers)
  • createArcTableSessionStorage (architect, Amazon DynamoDB)
  • custom storage with createSessionStorage

For this example, I’m going to use the createCookieSessionStorage function. This function is used for purely cookie-based sessions, where the session data itself is stored in the session cookie with the browser.

Create a new file named app/service/session.server.js and paste into the content below.

import { createCookieSessionStorage } from "@remix-run/node";

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
  },
});

Now, create a file app/service/auth.server.js for the Remix Auth configuration.

import { GitHubStrategy } from "remix-auth-github";
import { Authenticator } from "remix-auth";
import { sessionStorage } from "./session.server";

let gitHubStrategy = new GitHubStrategy(
  {
    clientID: "CLIENT_ID",
    clientSecret: "CLIENT_SECRET",
    callbackURL: "http://localhost:3000/auth/github/callback",
  },
  async ({ accessToken, extraParams, profile }) => {
    // Save/Get the user data from your DB or API using the tokens and profile
    return profile;
  }
);

export let authenticator = new Authenticator(sessionStorage);
authenticator.use(gitHubStrategy);

Here we import the Authenticator class and the sessionStorage object. We created a GitHub strategy and registered it with the authenticator.

The GitHubStrategy constructor takes a couple of parameters. We pass it the GitHub client ID, secret and callback URL. We also gave it a callback function that will be executed after the user has been authenticated from GitHub. That function has access to the returned accessToken, params and the user profile data. With such information, you can now create or retrieve user data from your database or wherever you choose to store and manage that data.

Don’t forget to replace the CLIENT_ID and CLIENT_SECRET placeholders with your GitHub OAuth app Client ID and Secret.

We tell the authenticator to use the gitHubStrategy by calling authenticator.use(gitHubStrategy).

Now that we have the strategy registered, let’s move to create the routes.

Create the GitHub authorization callback route by creating a file named app/routes/auth.github.callback.jsx:

import { authenticator } from "../service/auth.server";

export async function loader({ request }) {
  return authenticator.authenticate("github", request, {
    successRedirect: "/protected",
    failureRedirect: "/login",
  });
}

We call the authenticator.authenticate() method with the name of the strategy we want to use, the request object, and an object with the URLs we want the user to be redirected to after a success or a failure. The /protected route will be a page that only authenticated users have access to. We will create that in a moment, but first, let’s handle the login page.

Create a new file routes/login.jsx with the following content:

import { Form } from "@remix-run/react";
export default function Login() {
  return (
    <Form action="/auth/github" method="post">
      <button>Login with GitHub</button>
    </Form>
  );
}

This page has a basic form that posts to /auth/github when the form is submitted. You can also create the action in the same /login route, but I chose to have it separate for this example.

Next, let’s create the /auth/github route. Create a new file routes/auth.github.jsx and paste the content below into it.

import { redirect } from "@remix-run/node";
import { authenticator } from "../service/auth.server";

export async function loader() {
  return redirect("/login");
}

export async function action({ request }) {
  return authenticator.authenticate("github", request, {
    successRedirect: "/protected",
  });
}

This is a resource route that redirects to the /login route when loaded. The action authenticates the user. If the user is unauthenticated, they’re redirected to GitHub, otherwise, they’re taken to /protected route.

Adding Protected Routes

Now let’s create the /protected route. Create a new file named routes/protected.jsx, then copy and paste the snippet below into it.

import { json } from "@remix-run/node";
import { authenticator } from "../service/auth.server";
import { useLoaderData, Form } from "@remix-run/react";

export async function loader({ request }) {
  let user = await authenticator.isAuthenticated(request, {
    failureRedirect: "/login",
  });

  return json(user);
}

export default function Index() {
  const data = useLoaderData();

  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
      <h1>Welcome {data.displayName}</h1>
      <ul>
        <li>You have {data._json.followers} followers</li>
        <li>You're following {data._json.following} people</li>
      </ul>
      <Form action="/logout" method="post">
        <button>Logout? Click me</button>
      </Form>
    </div>
  );
}

Here we used the authenticator.isAuthenticated() method, with the option to redirect to /login if the user is not authenticated. If they are, we get the user object, which in our case is the GitHub profile data (recall we returned this data in auth.server.js). We return that data to the client and display some of the data to the user.

We added a form that posts to the /logout route when it’s clicked. Let’s implement that route.

Create a new file routes/logout.jsx and paste the code snippet below into it.

import { authenticator } from "../service/auth.server";

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

This route logs the user out by calling authenticator.logout() method. That method destroys the user session and redirects them to the /login route.

We’re almost done. Let’s add a login button to the Home page. Open the _index.jsx file and add the following markup in between the header element and the unordered list (i.e., after line 10).

<div>
  <Form action="/auth/github" method="post">
    <button>Login with GitHub</button>
  </Form>
</div>

Demo 👩🏽‍💻💃🏽

Start up the application using npm run dev and try it out in your browser.

Demo

That’s a Wrap

There you have it. 🚀 In less than 30 minutes, you have a fully protected app with single sign-on done through GitHub as the OAuth server. We used Remix Auth, which provides a comprehensive API for dealing with any form of authentication in Remix. Although we used the GitHub strategy (through the remix-auth-github package), you can create your own strategy and combine multiple strategies in one application.

I hope that was useful for you. Leave a comment if you have any questions, and check out my other posts about SSO in Next.js application.

Here’s the complete code for this post on GitHub.


Peter Mbanugo
About the Author

Peter Mbanugo

Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.