Avnology ID
SDKsTypeScript SDKGuides

React Integration

Integrate Avnology ID into React applications with hooks, protected routes, and session management.

React Integration

This guide shows how to integrate the TypeScript SDK with React 19 and React Router 7. For pre-built UI components (login form, registration form, MFA challenge), use @avnology/id-elements alongside the SDK.

Setup

npm install @avnology/sdk-typescript @avnology/id-elements

Create a shared client instance

// lib/avnology.ts
import { AvnologyId } from "@avnology/sdk-typescript";

export const avnology = new AvnologyId({
  baseUrl: import.meta.env.VITE_AVNOLOGY_BASE_URL,
  clientId: import.meta.env.VITE_AVNOLOGY_CLIENT_ID,
  credentials: "include",
  autoRefresh: true,
});

Provider setup

// root.tsx
import { AvnologyIdProvider } from "@avnology/id-elements";
import { avnology } from "./lib/avnology";

export default function Root() {
  return (
    <AvnologyIdProvider client={avnology}>
      <Outlet />
    </AvnologyIdProvider>
  );
}

Authentication hooks

useSession()

Access the current session in any component.

import { useSession } from "@avnology/id-elements";

function Dashboard() {
  const { session, isLoading, error } = useSession();

  if (isLoading) return <LoadingSpinner />;
  if (!session) return <Redirect to="/login" />;

  return (
    <div>
      <h1>Welcome, {session.identity.name?.first}</h1>
      <p>{session.identity.email}</p>
    </div>
  );
}

useLogout()

import { useLogout } from "@avnology/id-elements";

function LogoutButton() {
  const { logout, isLoading } = useLogout();

  return (
    <button onClick={() => logout()} disabled={isLoading}>
      {isLoading ? "Logging out..." : "Log out"}
    </button>
  );
}

Protected routes

Route guard with React Router loader

// lib/route-guards.ts
import { redirect } from "react-router";
import { avnology } from "./avnology";

export async function requireAuth() {
  const session = await avnology.getSession();
  if (!session) {
    throw redirect("/login");
  }
  return { session };
}

export async function requireMfa() {
  const session = await avnology.getSession();
  if (!session) {
    throw redirect("/login");
  }
  if (session.authenticatorAssuranceLevel !== "aal2") {
    throw redirect("/mfa");
  }
  return { session };
}

Using the guard in routes

// routes/dashboard.tsx
import { requireAuth } from "../lib/route-guards";

export async function loader() {
  return requireAuth();
}

export default function DashboardPage() {
  const { session } = useLoaderData<typeof loader>();
  return <Dashboard user={session.identity} />;
}

Permission-gated UI

import { usePermission } from "@avnology/id-elements";

function ProjectSettings({ projectId }: { projectId: string }) {
  const { allowed, isLoading } = usePermission({
    subject: `user:${userId}`,
    relation: "admin",
    object: `project:${projectId}`,
  });

  if (isLoading) return <LoadingSpinner />;
  if (!allowed) return <AccessDenied />;

  return <SettingsForm />;
}

Pre-built UI components

@avnology/id-elements provides complete auth UI components that handle the full flow.

import { LoginForm, RegistrationForm, MfaChallenge } from "@avnology/id-elements";

// Login page
function LoginPage() {
  return (
    <LoginForm
      onSuccess={(session) => navigate("/dashboard")}
      onMfaRequired={(flowId) => navigate(`/mfa?flow=${flowId}`)}
      showSocialProviders={["google", "github"]}
      showPasskeyOption
    />
  );
}

// Registration page
function RegisterPage() {
  return (
    <RegistrationForm
      onSuccess={(session) => navigate("/onboarding")}
      fields={["email", "password", "name"]}
    />
  );
}

// MFA challenge page
function MfaPage() {
  const [searchParams] = useSearchParams();
  return (
    <MfaChallenge
      flowId={searchParams.get("flow")!}
      onSuccess={(session) => navigate("/dashboard")}
    />
  );
}

See also

On this page