Aura Auth
Integrations

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:


What You'll Build

You will create a small React Router app with:

  • a shared src/lib/auth.ts server configuration
  • a catch-all src/routes/api.auth.$.tsx route for auth requests
  • an optional src/lib/auth-client.ts browser client
  • server and client examples for sign-in, session lookup, and sign-out

Project Structure

api.auth.$.tsx
auth.ts
auth-client.ts
.env.local

Environment Setup

Create a .env.local file at the root of your project to store secrets securely.

.env.local
# 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"
Never commit your .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.

src/lib/auth.ts
import { createAuth } from "@aura-stack/auth"

export const auth = createAuth({
  oauth: ["github"],
  basePath: "/api/auth",
  baseURL: "http://localhost:3000",
})

export const { handlers, api } = auth

basePath 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.

src/lib/auth-client.ts
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

routes/api.auth.$.tsx
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)
}
Aura Auth now handles all requests under /api/auth/*.

Usage

Server Side Rendering (SSR)

Sign In

routes/sign-in.tsx
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

routes/dashboard.tsx
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

routes/sign-out.tsx
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

components/sign-in-button.tsx
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

components/user-profile.tsx
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

components/sign-out-button.tsx
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 basePath aligned with the route segment. If your route lives at src/routes/api.auth.$.tsx, the auth configuration should use basePath: "/api/auth".
  • Use the shared auth module everywhere. Import src/lib/auth.ts from 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.

Resources

On this page