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/reactpackage installed
Install
pnpm add @keyloom/reactSessionProvider setup
Wrap your app with the SessionProvider to enable session management:
import { SessionProvider } from "@keyloom/react";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<SessionProvider basePath="/api/auth">{children}</SessionProvider>
</body>
</html>
);
}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 routeinitialData?: SessionResponse: Initial session data for SSR hydrationrefreshInterval?: number(default0): Auto-refetch session every N milliseconds (0 to disable)refetchOnWindowFocus?: boolean(defaulttrue): Refetch when window gains focus
Core Session Hooks
useSession()
Get the current session state and user information.
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 datastatus: "loading" | "authenticated" | "unauthenticated": Current authentication statuserror: AuthError | null: Any session-related errorsrefresh: () => Promise<void>: Function to refresh session data
useSessionStatus()
Get just the authentication status without other session data.
import { useSessionStatus } from "@keyloom/react";
export function AuthStatus() {
const status = useSessionStatus();
return <div>Status: {status}</div>;
}useUser()
Get user information with loading state.
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 dataloading: boolean: Whether user data is loadingerror: AuthError | null: Any user-related errors
useSessionRefresh()
Get session refresh function.
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.
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 functionloading: boolean: Whether login is in progresserror: AuthError | null: Any login errors
LoginParams:
email?: string: Email for credential loginpassword?: string: Password for credential loginprovider?: string: OAuth provider namecallbackUrl?: string: URL to redirect after OAuth
useLogout()
Handle user logout.
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 functionloading: boolean: Whether logout is in progresserror: AuthError | null: Any logout errors
useRegister()
Handle user registration.
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 functionloading: boolean: Whether registration is in progresserror: AuthError | null: Any registration errors
RegisterParams:
email: string: User emailpassword: string: User password
useOAuth()
Handle OAuth-only flows (alternative to useLogin for OAuth).
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 startingerror: AuthError | null: Any OAuth errors
usePasswordReset()
Handle password reset requests and resets.
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 resetreset: (params: { identifier: string; token: string; newPassword: string }) => Promise<{ ok: boolean }>: Reset passwordloading: boolean: Whether operation is in progresserror: AuthError | null: Any password reset errors
useEmailVerification()
Handle email verification.
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 emailloading: boolean: Whether verification is in progresserror: AuthError | null: Any verification errors
Form and Utility Hooks
useAuthForm()
Generic form state management for authentication forms.
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 valuessetValue: (key: keyof T, value: T[key]) => void: Update form fieldsubmit: (fn: (values: T) => Promise<void>) => Promise<void>: Submit form with async handlersubmitting: boolean: Whether form is submittingerror: AuthError | null: Any submission errorsreset: () => void: Reset form to initial state
useAuthRedirect()
Programmatic navigation utility.
import { useAuthRedirect } from "@keyloom/react";
export function RedirectButton() {
const { redirect } = useAuthRedirect();
return (
<button onClick={() => redirect("/dashboard")}>
Go to Dashboard
</button>
);
}useAuthError()
Error state management.
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.
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).
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.
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:
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
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
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:
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
refreshIntervalbased 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
basePathmatches your API route - Ensure Keyloom handler is configured correctly
Authentication redirects not working
- Check callback URLs in provider configuration
- Verify
callbackUrlparameter 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
- Installation: /docs/getting-started/installation
- Next.js Integration: /docs/nextjs/overview
- UI Components: /docs/ui/overview
Next steps
- Set up SessionProvider in your app
- Implement authentication UI with hooks
- Add error handling and loading states
How is this guide?