React Router (v7)
Build your first authentication flow with Aura Auth and React Router v7
This guide walks you through creating a complete authentication flow using Aura Auth in a React Router v7 (formerly Remix) application.
Overview
React Router v7 loaders and actions run on the server and receive the native Web Request object. That makes Aura Auth a strong fit for React Router's request-driven model: you can forward those requests directly to the shared handlers and keep auth logic in one place.
Before continuing, complete the installation and initial setup:
- Quick Start Guide to create your Aura Auth instance
- TypeScript Configuration for TypeScript-specific setup
- React Router Integration App for a fully working example
What You'll Build
You will create a small React Router app with:
- a shared
src/lib/auth.tsserver configuration - a catch-all
src/routes/api.auth.$.tsxroute for auth requests - an optional
src/lib/auth-client.tsbrowser client - server and client examples for sign-in, session lookup, and sign-out
Project Structure
Environment Setup
Create a .env.local file at the root of your project to store secrets securely.
# 32-bytes (256-bit) secret used to sign/encrypt sessions. Use a secure random value.
AURA_AUTH_SECRET="base64-or-hex-32-bytes"
AURA_AUTH_SALT="base64-or-hex-32-bytes".env.local file to version control. Use a secret manager in production.Setup Aura Auth
HTTP Handlers
Create a src/lib/auth.ts file to configure authentication and export the shared helpers used by loaders, actions, and client code.
import { createAuth } from "@aura-stack/auth"
export const auth = createAuth({
oauth: ["github"],
basePath: "/api/auth",
baseURL: "http://localhost:3000",
})
export const { handlers, api } = authbasePath must match the route mounted in src/routes/api.auth.$.tsx. baseURL should point to your local development server or deployed application URL.
Client API
To use Aura Auth's client-side features, create an auth-client.ts file to initialize the client.
import { createAuthClient } from "@aura-stack/auth/client"
export const authClient = createAuthClient({
basePath: "/api/auth",
baseURL: "http://localhost:3000",
})The baseURL should point to your server's URL, and basePath MUST match the path where your auth routes are mounted.
When you deploy, keep the same basePath but update baseURL to your production domain or environment-specific public URL.
Mount HTTP Handlers
import { handlers } from "~/lib/auth"
import type { Route } from "./+types/api.auth.$"
export const loader = async ({ request }: Route.LoaderArgs) => {
return handlers.GET(request)
}
export const action = async ({ request }: Route.ActionArgs) => {
return handlers.POST(request)
}/api/auth/*.Usage
Server Side Rendering (SSR)
Sign In
import { api } from "~/lib/auth"
import { Form } from "react-router"
import type { Route } from "./+types/sign-in"
export const action = async ({ request }: Route.ActionArgs) => {
const formData = await request.formData()
const provider = formData.get("provider") as string
return await api.signIn(provider, { request })
}
export default function SignInPage() {
return (
<div>
<h1>Login</h1>
<Form method="post">
<button type="submit" name="provider" value="github">
Sign in with GitHub
</button>
</Form>
</div>
)
}Get Session
import { api } from "~/lib/auth"
import type { Route } from "./+types/dashboard"
export const loader = async ({ request }: Route.LoaderArgs) => {
const session = await api.getSession({
headers: request.headers,
})
if (!session.authenticated) {
throw new Response("Unauthorized", { status: 401 })
}
return { session }
}
export default function DashboardPage({ loaderData }: Route.ComponentProps) {
const { user } = loaderData.session
return (
<div>
<h1>Welcome, {user?.name}!</h1>
<p>Email: {user?.email}</p>
</div>
)
}Sign Out
import { api } from "~/lib/auth"
import type { Route } from "./+types/sign-out"
export const action = async ({ request }: Route.ActionArgs) => {
return await api.signOut({ request })
}Client Side Rendering (CSR)
Use client components for interactive UI such as buttons, menus, and dialogs. The browser client is useful when you do not need a full route transition.
Sign In
import { authClient } from "~/lib/auth-client"
export const SignInButton = () => {
const handleSignIn = async () => {
await authClient.signIn("github", {
redirectTo: "/dashboard",
})
}
return <button onClick={handleSignIn}>Sign in with GitHub</button>
}If you want a redirect-less flow, set redirect: false and use the returned URL yourself.
Get Session
import { useEffect, useState } from "react"
import { authClient } from "~/lib/auth-client"
export const UserProfile = () => {
const [session, setSession] = useState(null)
useEffect(() => {
const fetchSession = async () => {
const { session } = await authClient.getSession()
setSession(session)
}
fetchSession()
}, [])
if (!session) return <p>Loading...</p>
return (
<div>
<h1>Welcome back, {session.user?.name}</h1>
<p>Email: {session.user?.email}</p>
</div>
)
}This works well for avatars, nav bars, and any component that should update after hydration.
Sign Out
import { authClient } from "~/lib/auth-client"
export const SignOutButton = () => {
const handleSignOut = async () => {
await authClient.signOut()
}
return <button onClick={handleSignOut}>Sign Out</button>
}Client-side sign-out is best for interactive controls inside menus or profile popovers.
Common Pitfalls
- Keep
basePathaligned with the route segment. If your route lives atsrc/routes/api.auth.$.tsx, the auth configuration should usebasePath: "/api/auth". - Use the shared auth module everywhere. Import
src/lib/auth.tsfrom loaders, actions, and the auth route so there is one auth configuration. - Protect pages in loaders when possible. If a route should never render for unauthenticated users, check the session before returning the page component.
- Use the browser client for interaction, not page protection. Client components are great for buttons and menus, but server-side checks are still the safer default for private routes.