FreeThe +45-page Authentication Analytics Whitepaper — measuring real login journeysDownload
Back to Overview

How to Build a Remix 2 Passkey Login

This tutorial shows how to implement passkeys in Remix apps. Using the Corbado Remix component adding passkeys is a matter of a few lines of code.

Blog-Post-Author
Lukas

Created: March 18, 2024

Updated: August 28, 2026

How to Build a Remix 2 Passkey Login

⚠️ Archived tutorial - outdated integration

This article uses Corbado Complete, a discontinued product. Corbado now offers passkey observability and authentication for enterprise CIAM

The tutorial below is preserved for reference only and is no longer maintained.

Key Facts
  • Corbado Remix component adds passkey authentication to Remix 2.8.1 apps with minimal code, installed via the @corbado/react and @corbado/types npm packages.
  • A client-only file workaround is required: naming component files with .client.tsx prevents Corbado packages from being included in Remix's server-side bundle during build.
  • The CorbadoAuth component manages all signup and login flows including fallback solutions. The PasskeyList component lets authenticated users view and manage their registered passkeys.
  • The Relying Party ID must be the bare domain only (no protocol, port or path) and is configured in Corbado's developer panel under Settings > General > URLs.

1. Introduction#

In this blog post, well be walking through the process of building a sample application with passkey authentication using Remix 2. Well cover how to embed the Corbado Remix component and implement passkey login functionality for a seamless user experience.

If you want to see the finished code, please have a look at our sample application repository on GitHub.

PasskeysCheatsheet Icon

Passkeys Cheatsheet. Practical guidance, rollout patterns and KPIs for passkey programs.

Get Cheat Sheet

The result looks as follows:

2. Prerequisites#

This tutorial assumes basic familiarity with Remix, HTML, CSS and JavaScript. Lets dive in! Moreover, you need to have Node and NPM installed on your machine.

3. Repository structure#

Lets first discuss the structure of our project (full GitHub repo):

. ├── .env ├── package.json └── app ├── entry.client.tsx ├── root.tsx ├── utilities │ ├── AuthComponent.client.tsx │ └── ProfileComponent.client.tsx └── routes ├── _index.tsx └── profile.tsx

The rest of the files of this project can be ignored for the purpose of this tutorial.

Slack Icon

Become part of our Passkeys Community for updates & support.

Join

4. Set up the Remix Project#

In the following, we explain step-by-step what needs to be done to successfully set up the Remix project.

Lets start out by initializing a new Remix project. In this tutorial, were using Remix version 2.8.1:

npx create-remix@latest --template remix-run/remix/templates/vite

You will be prompted the following options:

Where should we create your new project?

  • If you already have a project folder, just choose "." otherwhise choose any name you like

Initialize a new git Repository?

  • We recommend to opt for "yes" here

Install dependencies with npm?

  • Choose yes

If you created a new project directory, navigate into it with:

cd <your directory name>

If you run

npm run dev

the sample skeleton application starts at http://localhost:5173:

5. Set up the Corbado Remix Component for Passkey Authentication#

5.1 Set up Your Corbado Account and Project#

Visit the Corbado developer panel to sign up and create your account (you'll see the passkey sign-up in action here!).

In the project setup wizard, begin by selecting an appropriate name for your project. For the product selection, opt for "Corbado Complete". Subsequently, specify your technology stack and select "DEV along with "Corbado session management" options. Afterwards, youll get more foundational setup guidance. The subsequent sections of this article will delve into the integration part.

Next, we set the Application URL and Relying Party ID inside Settings > General > URLs to the following values (see explanation below):

  • Application URL: Provide the URL where your application is running, here: http://localhost:5173
  • Relying Party ID: Provide the domain (no protocol, no port and no path) where passkeys should be bound to, here: localhost

5.2 Embed the Remix component in your Frontend#

First, we need to install the React component with its corresponding types, so that we have full typescript support:

npm i @corbado/react @corbado/types

First, delete the server entry file under /app/entry.server.tsx as we wont need any custom logic for the server entrypoint here. If this file isnt present, Remix will automatically handle the logic for us.

Create a .env file and put your Corbado Project Id found under Settings > General > Project info inside.

VITE_PUBLIC_CORBADO_PROJECT_ID=<Your project ID>

Now, head into the entry.client.tsx file and wrap your entire application with the CorbadoProvider. Hand it your project id from the environment and set darkMode to off.

import { RemixBrowser } from "@remix-run/react"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { CorbadoProvider } from "@corbado/react"; startTransition(() => { hydrateRoot( document, <StrictMode> <CorbadoProvider projectId={import.meta.env.VITE_PUBLIC_CORBADO_PROJECT_ID} darkMode="off" > <RemixBrowser /> </CorbadoProvider> </StrictMode>, ); });

For all of the components and hooks were going to use from Corbado well need a workaround in order to prevent the Corbado React Package from being inlcuded inside the server bundle during build. This can be achieved by only using the Corbado React package inside files that have .client before their file extension (e.g. someComponent.client.tsx) and providing a fallback for the components during ssr. This can be achieved by exporting a clientLoader as well as a HydrateFallback from your Components (find documentation on this here). Youll see this in action in the coming steps.

Next, well need some utilities which we are going to place inside /app/utilities. Start by creating an AuthComponent.client.tsx file. The useCorbado hook provides us with authentication related information and the CorbadoAuth component manages all signup and login flows including fallback solutions for us.

import { CorbadoAuth, useCorbado } from "@corbado/react"; import { useNavigate } from "@remix-run/react"; export default function AuthComponent() { const { isAuthenticated, loading } = useCorbado(); const navigate = useNavigate(); if (isAuthenticated && !loading) { navigate("/profile"); } function onLoggedIn() { navigate("/profile"); } return <CorbadoAuth onLoggedIn={onLoggedIn} />; }

If the user is already authenticated or once he authenticates, well redirect him to the /profile route.

Remove the skeleton code from _index.tsx and display our component inside of the Index function. For the mentioned workaround we now have to export two more functions:

The hydrateFallback which will tell Remix what fallback we want to use during server side rendering as well as a clientLoader to enforce rendering on the client. You can read more about this here.

import type { MetaFunction } from "@remix-run/node"; import AuthComponent from "~/utilities/AuthComponent.client"; export const meta: MetaFunction = () => { return [ { title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }, ]; }; // this is needed so ssr of the AuthComponent is prevented export function clientLoader() { return null; } // Provide a fallback for ssr as trying to render Corbado Components on the server doesn't work export function HydrateFallback() { return <p>Loading...</p>; } export default function Index() { return ( <div> <AuthComponent /> </div> ); }

5.3 Set up the Profile Page#

Now, lets create the Profile page. Create another utility called ProfileComponent.client.tsx. Here, well use useCorbado once again to retrieve authentication state and a logout utility as well as the useCorbadoSession hook to retrieve the user object, which lets us access information like his name and email. If the user isnt authenticated, well redirect him to the homepage where our auth component is displayed. We also incldue the PasskeyList component which lets the user view and manage his existing passkeys.

import { PasskeyList, useCorbado, useCorbadoSession } from "@corbado/react"; import { useNavigate } from "@remix-run/react"; export default function Profile() { const navigate = useNavigate(); const { logout, isAuthenticated, loading } = useCorbado(); const { user } = useCorbadoSession(); if (!isAuthenticated && !loading) { navigate("/"); } return ( <div style={{ maxWidth: "600px", margin: "0 auto" }}> <h1>Profile</h1> <p>Name: {user?.name}</p> <p>Email: {user?.email}</p> <button onClick={logout}>Logout</button> <PasskeyList /> </div> ); }

Lastly, create the profile route by creating a profile.tsx file under /app/routes and apply the same workaround as in _index.tsx.

import ProfileComponent from "~/utilities/ProfileComponent.client"; export function clientLoader() { return null; } export function HydrateFallback() { return <p>Loading...</p>; } export default function Profile() { return <ProfileComponent />; }

5.4 Start Using Passkeys#

If everything is set up and installed, run the application with

npm run dev

You should see the following screen:

After successful sign up / login, you see the profile page:

6. How Corbado can help#

Sections 5.2 and 5.3 spend most of their effort on one problem: the passkey UI has to run in the browser, so it ends up in .client.tsx files with a HydrateFallback behind it. That work only pays off if the passkey prompt was worth rendering for the visitor who arrived. Corbado Connect makes that call before the ceremony starts.

See how Corbado Connect ships this →
  • The decision sees more than the user agent. Passkey Intelligence is handed a detection object that already knows whether this environment holds a local platform passkey, a synced one, a cross-platform key or one living in a password manager. It also knows what the account holds and what happened here before, down to recently aborted appends and the cooldowns that follow them.
  • Rollout is a separate ruleset with separate conditions. Those are client information: web, app, webview, browser, platform and platform version, device brand and model, feature flags, plus a custom expression when the built-in list runs out. A ruleset can be simulated against the last seven days of recorded traffic before you turn it on.
  • Client-only mounting is the supported shape. The components ship as React components in @corbado/connect-react, so the .client.tsx and clientLoader pattern this tutorial builds is what a Connect integration in Remix looks like as well.

7. Conclusion#

This tutorial showed how easy it is to add passwordless authentication with passkeys to a Remix app using Corbado. Besides the passkey-first authentication, Corbado provides simple session management, that we used for a retrieval of basic user data. If you want to add Corbado to your existing app with existing users, see our Corbado Connect documentation.

Corbado

About Corbado

Corbado is the Passkey Intelligence Platform for large-scale CIAM teams running consumer authentication. We help you see what IDP logs and generic analytics tools can't: where passkeys, passwords, OTP, social login and fallback journeys succeed, stall or fail, which devices and browsers create friction, and when an OS update silently breaks login. Two products: Corbado Observe layers process mining and observability across authentication journeys. Corbado Connect adds managed passkeys with analytics built in alongside your IDP. VicRoads runs passkeys for 5M+ users with Corbado (+80% passkey activation). Talk to a Passkey Expert

Frequently Asked Questions#

How do I stop Corbado React components from breaking Remix server-side rendering?#

Name any file containing Corbado components with a .client.tsx extension to exclude it from the server bundle. Additionally, export a clientLoader function and a HydrateFallback from each affected route to provide a fallback during SSR and enforce client-only rendering.

What hooks does the Corbado React package provide for managing authentication state in a Remix app?#

The useCorbado hook exposes isAuthenticated, loading state and a logout utility. The useCorbadoSession hook provides access to the user object, including properties like name and email, for use on protected pages like a profile route.

How do I configure passkey authentication for local Remix development using Corbado?#

In the Corbado developer panel under Settings > General > URLs, set the Application URL to http://localhost:5173 and the Relying Party ID to localhost with no protocol, port or path. This correctly binds passkeys to your local development domain.

What is the minimum code needed to wrap a Remix 2 app with Corbado passkey support?#

In entry.client.tsx, import CorbadoProvider from @corbado/react and wrap the RemixBrowser component with it, passing your project ID from an environment variable (VITE_PUBLIC_CORBADO_PROJECT_ID) and a darkMode setting. The provider handles session management and authentication context for the entire app.

See how Corbado fits your passkey rollout and existing authentication stack.

Explore the Console

Share this article


LinkedInTwitterFacebook