KeyloomKeyloom
Keyloom Auth is currently in beta. Feedback and contributions are welcome!
React

React Hooks

Complete React hooks reference for Keyloom - session management, authentication actions, forms, and utilities.

React Hooks

React hooks and utilities for working with Keyloom sessions and auth flows from the client. Works with the Next.js handler routes under /api/auth.

Prerequisites

  • React 18+ or Next.js 13+
  • Keyloom handler configured at /api/auth/[...keyloom]
  • @keyloom/react package installed

Install

pnpm add @keyloom/react

SessionProvider setup

Wrap your app with the SessionProvider to enable session management:

app/layout.tsx (Next.js App Router)
import { SessionProvider } from "@keyloom/react";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <SessionProvider basePath="/api/auth">{children}</SessionProvider>
      </body>
    </html>
  );
}
pages/_app.tsx (Next.js Pages Router)
import { SessionProvider } from "@keyloom/react";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <SessionProvider basePath="/api/auth">
      <Component {...pageProps} />
    </SessionProvider>
  );
}

SessionProvider options:

  • basePath (default /api/auth): Points to your Keyloom API route
  • initialData?: SessionResponse: Initial session data for SSR hydration
  • refreshInterval?: number (default 0): Auto-refetch session every N milliseconds (0 to disable)
  • refetchOnWindowFocus?: boolean (default true): Refetch when window gains focus

Core Session Hooks

useSession()

Get the current session state and user information.

components/Profile.tsx
import { useSession } from "@keyloom/react";

export function Profile() {
  const { data, status, error, refresh } = useSession();

  if (status === "loading") return <div>Loading...</div>;
  if (status === "unauthenticated") return <div>Please sign in</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Welcome, {data.user?.name}!</h1>
      <p>Email: {data.user?.email}</p>
      <button onClick={refresh}>Refresh Session</button>
    </div>
  );
}

Return values:

  • data: { session: Session | null; user: User | null }: Session and user data
  • status: "loading" | "authenticated" | "unauthenticated": Current authentication status
  • error: AuthError | null: Any session-related errors
  • refresh: () => Promise<void>: Function to refresh session data

useSessionStatus()

Get just the authentication status without other session data.

components/AuthStatus.tsx
import { useSessionStatus } from "@keyloom/react";

export function AuthStatus() {
  const status = useSessionStatus();

  return <div>Status: {status}</div>;
}

useUser()

Get user information with loading state.

components/UserInfo.tsx
import { useUser } from "@keyloom/react";

export function UserInfo() {
  const { user, loading, error } = useUser();

  if (loading) return <div>Loading user...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>Not signed in</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Return values:

  • user: User | null: Current user data
  • loading: boolean: Whether user data is loading
  • error: AuthError | null: Any user-related errors

useSessionRefresh()

Get session refresh function.

components/RefreshButton.tsx
import { useSessionRefresh } from "@keyloom/react";

export function RefreshButton() {
  const { refresh } = useSessionRefresh();

  return <button onClick={refresh}>Refresh Session</button>;
}

Authentication Action Hooks

useLogin()

Handle email/password login and OAuth flows.

components/LoginForm.tsx
import { useLogin } from "@keyloom/react";
import { useState } from "react";

export function LoginForm() {
  const { login, loading, error } = useLogin();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleEmailLogin = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await login({ email, password });
    if (result.ok) {
      console.log("Login successful");
    }
  };

  const handleOAuthLogin = async (provider: string) => {
    await login({ provider, callbackUrl: "/dashboard" });
  };

  return (
    <div className="space-y-4">
      {error && <div className="text-red-600">{error.message}</div>}

      <form onSubmit={handleEmailLogin} className="space-y-2">
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
          required
        />
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Password"
          required
        />
        <button type="submit" disabled={loading}>
          {loading ? "Signing in..." : "Sign In"}
        </button>
      </form>

      <div className="space-x-2">
        <button onClick={() => handleOAuthLogin("github")}>
          Sign in with GitHub
        </button>
        <button onClick={() => handleOAuthLogin("google")}>
          Sign in with Google
        </button>
      </div>
    </div>
  );
}

Return values:

  • login: (params: LoginParams) => Promise<{ ok: boolean; session?: Session }>: Login function
  • loading: boolean: Whether login is in progress
  • error: AuthError | null: Any login errors

LoginParams:

  • email?: string: Email for credential login
  • password?: string: Password for credential login
  • provider?: string: OAuth provider name
  • callbackUrl?: string: URL to redirect after OAuth

useLogout()

Handle user logout.

components/LogoutButton.tsx
import { useLogout } from "@keyloom/react";

export function LogoutButton() {
  const { logout, loading, error } = useLogout();

  const handleLogout = async () => {
    const result = await logout();
    if (result.ok) {
      console.log("Logout successful");
    }
  };

  return (
    <div>
      {error && <div className="text-red-600">{error.message}</div>}
      <button onClick={handleLogout} disabled={loading}>
        {loading ? "Signing out..." : "Sign Out"}
      </button>
    </div>
  );
}

Return values:

  • logout: () => Promise<{ ok: boolean }>: Logout function
  • loading: boolean: Whether logout is in progress
  • error: AuthError | null: Any logout errors

useRegister()

Handle user registration.

components/RegisterForm.tsx
import { useRegister } from "@keyloom/react";
import { useState } from "react";

export function RegisterForm() {
  const { register, loading, error } = useRegister();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await register({ email, password });
    if (result.ok) {
      console.log("Registration successful");
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      {error && <div className="text-red-600">{error.message}</div>}

      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? "Creating account..." : "Sign Up"}
      </button>
    </form>
  );
}

Return values:

  • register: (params: RegisterParams) => Promise<{ ok: boolean }>: Registration function
  • loading: boolean: Whether registration is in progress
  • error: AuthError | null: Any registration errors

RegisterParams:

  • email: string: User email
  • password: string: User password

useOAuth()

Handle OAuth-only flows (alternative to useLogin for OAuth).

components/OAuthButtons.tsx
import { useOAuth } from "@keyloom/react";

export function OAuthButtons() {
  const { login, loading, error } = useOAuth();

  const handleOAuth = async (provider: string) => {
    await login({ provider, callbackUrl: "/dashboard" });
  };

  return (
    <div className="space-y-2">
      {error && <div className="text-red-600">{error.message}</div>}

      <button onClick={() => handleOAuth("github")} disabled={loading}>
        Sign in with GitHub
      </button>
      <button onClick={() => handleOAuth("google")} disabled={loading}>
        Sign in with Google
      </button>
    </div>
  );
}

Return values:

  • login: (params: OAuthParams) => void: OAuth login function (redirects browser)
  • loading: boolean: Whether OAuth flow is starting
  • error: AuthError | null: Any OAuth errors

usePasswordReset()

Handle password reset requests and resets.

components/PasswordResetForm.tsx
import { usePasswordReset } from "@keyloom/react";
import { useState } from "react";

export function PasswordResetForm() {
  const { request, reset, loading, error } = usePasswordReset();
  const [email, setEmail] = useState("");
  const [token, setToken] = useState("");
  const [newPassword, setNewPassword] = useState("");
  const [step, setStep] = useState<"request" | "reset">("request");

  const handleRequest = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await request({ email });
    if (result.ok) {
      setStep("reset");
    }
  };

  const handleReset = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await reset({ identifier: email, token, newPassword });
    if (result.ok) {
      console.log("Password reset successful");
    }
  };

  return (
    <div className="space-y-4">
      {error && <div className="text-red-600">{error.message}</div>}

      {step === "request" ? (
        <form onSubmit={handleRequest} className="space-y-2">
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email"
            required
          />
          <button type="submit" disabled={loading}>
            {loading ? "Sending..." : "Send Reset Email"}
          </button>
        </form>
      ) : (
        <form onSubmit={handleReset} className="space-y-2">
          <input
            type="text"
            value={token}
            onChange={(e) => setToken(e.target.value)}
            placeholder="Reset Token"
            required
          />
          <input
            type="password"
            value={newPassword}
            onChange={(e) => setNewPassword(e.target.value)}
            placeholder="New Password"
            required
          />
          <button type="submit" disabled={loading}>
            {loading ? "Resetting..." : "Reset Password"}
          </button>
        </form>
      )}
    </div>
  );
}

Return values:

  • request: (params: { email: string }) => Promise<{ ok: boolean }>: Request password reset
  • reset: (params: { identifier: string; token: string; newPassword: string }) => Promise<{ ok: boolean }>: Reset password
  • loading: boolean: Whether operation is in progress
  • error: AuthError | null: Any password reset errors

useEmailVerification()

Handle email verification.

components/EmailVerifyForm.tsx
import { useEmailVerification } from "@keyloom/react";
import { useState } from "react";

export function EmailVerifyForm() {
  const { verify, loading, error } = useEmailVerification();
  const [email, setEmail] = useState("");
  const [token, setToken] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await verify({ identifier: email, token });
    if (result.ok) {
      console.log("Email verified successfully");
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      {error && <div className="text-red-600">{error.message}</div>}

      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="text"
        value={token}
        onChange={(e) => setToken(e.target.value)}
        placeholder="Verification Token"
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? "Verifying..." : "Verify Email"}
      </button>
    </form>
  );
}

Return values:

  • verify: (params: { identifier: string; token: string }) => Promise<{ ok: boolean }>: Verify email
  • loading: boolean: Whether verification is in progress
  • error: AuthError | null: Any verification errors

Form and Utility Hooks

useAuthForm()

Generic form state management for authentication forms.

components/CustomLoginForm.tsx
import { useAuthForm, useLogin } from "@keyloom/react";

export function CustomLoginForm() {
  const { login } = useLogin();
  const { values, setValue, submit, submitting, error, reset } = useAuthForm({
    email: "",
    password: "",
  });

  const handleSubmit = async () => {
    await submit(async ({ email, password }) => {
      await login({ email, password });
    });
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }} className="space-y-4">
      {error && <div className="text-red-600">{error.message}</div>}

      <input
        type="email"
        value={values.email}
        onChange={(e) => setValue("email", e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={values.password}
        onChange={(e) => setValue("password", e.target.value)}
        placeholder="Password"
      />
      <button type="submit" disabled={submitting}>
        {submitting ? "Signing in..." : "Sign In"}
      </button>
      <button type="button" onClick={reset}>
        Reset Form
      </button>
    </form>
  );
}

Return values:

  • values: T: Current form values
  • setValue: (key: keyof T, value: T[key]) => void: Update form field
  • submit: (fn: (values: T) => Promise<void>) => Promise<void>: Submit form with async handler
  • submitting: boolean: Whether form is submitting
  • error: AuthError | null: Any submission errors
  • reset: () => void: Reset form to initial state

useAuthRedirect()

Programmatic navigation utility.

components/RedirectButton.tsx
import { useAuthRedirect } from "@keyloom/react";

export function RedirectButton() {
  const { redirect } = useAuthRedirect();

  return (
    <button onClick={() => redirect("/dashboard")}>
      Go to Dashboard
    </button>
  );
}

useAuthError()

Error state management.

components/ErrorDisplay.tsx
import { useAuthError } from "@keyloom/react";

export function ErrorDisplay() {
  const { error, setError } = useAuthError();

  if (!error) return null;

  return (
    <div className="bg-red-50 border border-red-200 p-4 rounded">
      <p>{error.message}</p>
      <button onClick={() => setError(null)}>Dismiss</button>
    </div>
  );
}

useAuthGuard()

Route protection with automatic redirects.

components/ProtectedPage.tsx
import { useAuthGuard } from "@keyloom/react";

export function ProtectedPage({ children }: { children: React.ReactNode }) {
  const { allowed, status } = useAuthGuard({
    requireAuth: true,
    redirectTo: "/login",
  });

  if (status === "loading") return <div>Loading...</div>;
  if (!allowed) return null; // Will redirect

  return <>{children}</>;
}

usePermissions()

Basic RBAC utilities (placeholder implementation).

components/RoleBasedContent.tsx
import { usePermissions } from "@keyloom/react";

export function RoleBasedContent() {
  const { hasRole, roles } = usePermissions();

  return (
    <div>
      {hasRole("admin") && <button>Admin Action</button>}
      <p>User roles: {roles.join(", ")}</p>
    </div>
  );
}

useProfile()

Get user profile information.

components/ProfileCard.tsx
import { useProfile } from "@keyloom/react";

export function ProfileCard() {
  const { user } = useProfile();

  if (!user) return <div>Not signed in</div>;

  return (
    <div className="p-4 border rounded">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

Non-Hook Utilities

For use outside of React components:

utils/auth.ts
import { getSession, signIn, signOut } from "@keyloom/react";

// Get session data
const sessionData = await getSession("/api/auth");

// Sign in programmatically
await signIn({ email: "user@example.com", password: "password" }, "/api/auth");
await signIn({ provider: "github", callbackUrl: "/dashboard" }, "/api/auth");

// Sign out programmatically
await signOut("/api/auth");

Advanced Patterns

Protected Routes

components/ProtectedRoute.tsx
import { useSession } from "@keyloom/react";
import { useRouter } from "next/router";
import { useEffect } from "react";

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { status } = useSession();
  const router = useRouter();

  useEffect(() => {
    if (status === "unauthenticated") {
      router.push("/auth/signin");
    }
  }, [status, router]);

  if (status === "loading") return <div>Loading...</div>;
  if (status === "unauthenticated") return null;

  return <>{children}</>;
}

Conditional Rendering

components/ConditionalContent.tsx
import { useSession, usePermissions } from "@keyloom/react";

export function ConditionalContent() {
  const { data, status } = useSession();
  const { hasRole } = usePermissions();

  return (
    <div>
      {status === "authenticated" && <p>Welcome back, {data.user?.name}!</p>}
      {hasRole("admin") && <button>Admin Panel</button>}
      {status === "unauthenticated" && <p>Please sign in to continue</p>}
    </div>
  );
}

Error Handling

All hooks return error states that you can handle:

components/ErrorHandling.tsx
import { useLogin, useSession } from "@keyloom/react";

export function ErrorHandling() {
  const { data, status, error: sessionError } = useSession();
  const { login, loading, error: loginError } = useLogin();

  const handleLogin = async () => {
    const result = await login({ provider: "github" });
    if (!result.ok) {
      console.error("Login failed");
    }
  };

  return (
    <div>
      {sessionError && <div>Session Error: {sessionError.message}</div>}
      {loginError && <div>Login Error: {loginError.message}</div>}

      <button onClick={handleLogin} disabled={loading}>
        Sign In
      </button>
    </div>
  );
}

Performance Considerations

  • Session Caching: Sessions are cached and automatically refreshed
  • Refetch Strategy: Configure refreshInterval based on your security needs
  • Bundle Size: Import only the hooks you need
  • SSR Compatibility: All hooks work with Next.js SSR and SSG

Troubleshooting

Session not loading

  • Verify SessionProvider wraps your app
  • Check basePath matches your API route
  • Ensure Keyloom handler is configured correctly

Authentication redirects not working

  • Check callback URLs in provider configuration
  • Verify callbackUrl parameter is valid
  • Ensure HTTPS in production

Hooks not working

  • Ensure you're using hooks inside SessionProvider
  • Check that you're calling hooks from React components
  • Verify proper import paths

See also

Next steps

  • Set up SessionProvider in your app
  • Implement authentication UI with hooks
  • Add error handling and loading states

How is this guide?