Authenticate with SlashID in your Remix app
Let’s say you’re creating a web app with Remix and you are looking for some third party auth solution. After initial research, you have plenty of candidates - how do you pick one? Of course, there are several aspects to this decision, but let’s focus purely on the technical side.
Authentication library for the server
When working with frameworks like Remix, you have slightly different concerns than a standard client-rendered React application. There are certain questions that need to be answered before committing to one solution.
Here’s an example evaluation of an authentication library:
- First of all - does it rely on any browser APIs? Can I safely use it on the server?
- Does it support server-side rendering (SSR)? Can I pre-render the UI components on the server?
- Additionally, does SSR give me any advantages over client-side rendering (CSR) when working with the library?
This minimal set of questions should help you eliminate some of the libraries. Now, let’s focus on SlashID and why you should consider choosing us for your next project!
Why choose SlashID?
SlashID React SDK is SSR-friendly (even though it makes use of some browser-specific APIs). This means that you can safely use it both in the browser and on the server. It is extremely important when working with modern SSR frameworks, such as Remix.
The React SDK provides you with a set of UI components which you can use for building your application, depending on your needs. If you don’t want to build the Login page yourself, you can use our stateful Login Form. Need to render different things for authenticated and guest users? We got you with the LoggedIn / LoggedOut utility components!
All of the UI components provided by SlashID React SDK can safely be pre-rendered on the server for the optimal performance of your application. This guide shows you how to easily build an authentication flow with SlashID in your Remix application.
Setup
After installing the React SDK, please make sure to add the SlashID SDKs to the server bundle using the configuration below.
Server bundle
Due to a known issue with Remix and importing ESM packages at runtime, it is necessary to bundle SlashID Core SDK and React SDK directly into the server build:
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
serverDependenciesToBundle: ["@slashid/slashid", "@slashid/react"],
// ...
}
Add the providers
Create a wrapper component with the necessary providers and configuration:
import type { Factor } from "@slashid/slashid"
import type { ReactNode } from "react"
import { SlashIDProvider, ConfigurationProvider } from "@slashid/react"
type Props = {
children: ReactNode,
}
export const SlashID = ({ children }: Props) => {
const factors: Factor[] = [{ method: "email_link" }]
return (
<SlashIDProvider baseApiUrl="https://api.slashid.com" oid="ORGANIZATION_ID" tokenStorage="localStorage">
<ConfigurationProvider factors={factors}>{children}</ConfigurationProvider>
</SlashIDProvider>
)
}
Replace ORGANIZATION_ID with your /id organization id, you can find it under configuration in the /id Console.
From here, you need to wrap the application with the provider components you just configured. There are two ways to do this. In the next section, you'll learn about both, along with some pros and cons. This will help you decide which option is best suited for your use case.
Root route
The most straightforward way of wrapping your application with the providers is to do it in the root route. This way, all of the routes will be wrapped with the providers.
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<SlashID>
<Outlet />
</SlashID>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
)
}
This is the recommended option. The root route integration is often considered the simplest way to get started for most use cases.
Entry files
Entry files let you have different configuration for the server and the browser. Use the wrapper in both client and server entry files as follows:
// ... imports
import { SlashID } from "./slash-id"
function hydrate() {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<SlashID>
<RemixBrowser />
</SlashID>
</StrictMode>
)
})
}
// ...
// ... imports
import { SlashID } from "./slash-id";
//... function handleBotRequest() ...
const { pipe, abort } = renderToPipeableStream(
<SlashID>
<RemixServer context={remixContext} url={request.url} />
</SlashID>,
// ... function handleBrowserRequest() ...
const { pipe, abort } = renderToPipeableStream(
<SlashID>
<RemixServer context={remixContext} url={request.url} />
</SlashID>,
// ...
Integration with entry files should be done in cases when you want to have different configurations for the server and the browser.
Additionally, you should use this approach if your root route depends on the SlashID context.
That’s it! Now your Remix app is connected to SlashID.
Authentication
You can easily build conditionally rendered UI for authenticated and not authenticated users with SlashID React SDK components:
import type { LinksFunction } from "@remix-run/node"
import { LoggedOut, LoggedIn, Form } from "@slashid/react"
import slashIDstyles from "@slashid/react/style.css"
export const links: LinksFunction = () => [{ rel: "stylesheet", href: slashIDstyles }]
export default function Index() {
return (
<div>
<LoggedOut>
{/* Will only render for when user is not authenticated */}
<div>
<Form />
</div>
</LoggedOut>
<LoggedIn>
{/* Content for authenticated users only */}
You’re logged in with /id!
</LoggedIn>
</div>
)
}
Check out the example Remix app for more details.