Using useAuth, AuthProvider, and getServerSession
The SDK provides React hooks for seamless client-side authentication in React and Next.js applications. These hooks handle session management, loading states, and authentication actions automatically.
The SDK includes three main components for React integration:
AuthProvider - Context provider for managing session stateuseAuth() - Hook for accessing session, loading state, and auth methodsgetServerSession() - Server-side session helper for Server Componentsnpm install @warpy-auth-sdk/core
# The hooks are included in the core packageThe AuthProvider must wrap your application to provide authentication context to all child components.
Root layout with AuthProvider
import { AuthProvider } from "@warpy-auth-sdk/core/hooks";
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<AuthProvider>
{children}
</AuthProvider>
</body>
</html>
);
}The AuthProvider:
GET /api/auth/sessionsignOut and refetch methodsThe useAuth hook provides access to the current session, loading state, and authentication methods.
interface UseAuthReturn {
// Current session (null if not authenticated)
session: Session | null;
// Loading state (true during initial fetch)
loading: boolean;
// Sign out the current user
signOut: () => Promise<void>;
// Manually refetch the session
refetch: () => Promise<void>;
}Display user information
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
export default function Profile() {
const { session, loading } = useAuth();
if (loading) {
return <div>Loading...</div>;
}
if (!session) {
return <div>Not authenticated</div>;
}
return (
<div>
<h1>Welcome, {session.user.name}!</h1>
<p>Email: {session.user.email}</p>
{session.user.picture && (
<img src={session.user.picture} alt="Profile" />
)}
</div>
);
}Redirect unauthenticated users
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export default function Dashboard() {
const { session, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !session) {
router.push("/login");
}
}, [session, loading, router]);
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900" />
</div>
);
}
if (!session) {
return null; // Will redirect
}
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {session.user.name}</p>
</div>
);
}Sign out the current user
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useRouter } from "next/navigation";
export default function SignOutButton() {
const { signOut } = useAuth();
const router = useRouter();
const handleSignOut = async () => {
await signOut();
router.push("/login");
};
return (
<button
onClick={handleSignOut}
className="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700"
>
Sign Out
</button>
);
}Show different UI based on auth state
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import Link from "next/link";
export default function Navigation() {
const { session, loading, signOut } = useAuth();
if (loading) {
return (
<nav>
<div className="animate-pulse bg-gray-200 h-10 w-20 rounded" />
</nav>
);
}
return (
<nav className="flex items-center justify-between p-4 bg-white shadow">
<Link href="/" className="text-xl font-bold">
My App
</Link>
<div className="space-x-4">
{session ? (
<>
<span>Hello, {session.user.name}</span>
<Link
href="/dashboard"
className="text-blue-600 hover:text-blue-800"
>
Dashboard
</Link>
<button
onClick={signOut}
className="text-red-600 hover:text-red-800"
>
Sign Out
</button>
</>
) : (
<>
<Link
href="/login"
className="text-blue-600 hover:text-blue-800"
>
Sign In
</Link>
<Link
href="/signup"
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
>
Sign Up
</Link>
</>
)}
</div>
</nav>
);
}The getServerSession helper retrieves the session in Server Components, Server Actions, and Route Handlers.
Server component with session access
import { getServerSession } from "@warpy-auth-sdk/core/hooks/server";
import { redirect } from "next/navigation";
export default async function ProfilePage() {
const session = await getServerSession();
if (!session) {
redirect("/login");
}
return (
<div>
<h1>Profile</h1>
<p>Name: {session.user.name}</p>
<p>Email: {session.user.email}</p>
<p>User ID: {session.user.id}</p>
</div>
);
}Server action with session validation
'use server';
import { getServerSession } from "@warpy-auth-sdk/core/hooks/server";
import { revalidatePath } from "next/cache";
export async function updateProfile(formData: FormData) {
const session = await getServerSession();
if (!session) {
throw new Error("Unauthorized");
}
const name = formData.get("name") as string;
// Update user in database
await db.user.update({
where: { id: session.user.id },
data: { name },
});
revalidatePath("/profile");
return { success: true };
}API route with session validation
import { getServerSession } from "@warpy-auth-sdk/core/hooks/server";
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const session = await getServerSession();
if (!session) {
return Response.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// Fetch user data from database
const user = await db.user.findUnique({
where: { id: session.user.id },
include: { posts: true },
});
return Response.json({ user });
}The session object has the following structure:
interface Session {
// User information
user: {
id: string;
email: string;
name?: string;
picture?: string;
};
// Session metadata
expires: string; // ISO timestamp
type?: string; // 'oauth', 'email', 'mcp-agent', etc.
// Custom fields (from session callback)
[key: string]: any;
}Reusable loading guard
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { ReactNode } from "react";
interface LoadingGuardProps {
children: ReactNode;
fallback?: ReactNode;
}
export function LoadingGuard({ children, fallback }: LoadingGuardProps) {
const { loading } = useAuth();
if (loading) {
return fallback || (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900" />
</div>
);
}
return <>{children}</>;
}
// Usage:
// <LoadingGuard fallback={<Spinner />}>
// <Dashboard />
// </LoadingGuard>Protect routes with a reusable guard
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useRouter } from "next/navigation";
import { ReactNode, useEffect } from "react";
interface AuthGuardProps {
children: ReactNode;
redirectTo?: string;
}
export function AuthGuard({ children, redirectTo = "/login" }: AuthGuardProps) {
const { session, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !session) {
router.push(redirectTo);
}
}, [session, loading, router, redirectTo]);
if (loading) {
return <div>Loading...</div>;
}
if (!session) {
return null; // Will redirect
}
return <>{children}</>;
}
// Usage:
// <AuthGuard redirectTo="/login">
// <ProtectedContent />
// </AuthGuard>Protect routes by user role
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useRouter } from "next/navigation";
import { ReactNode, useEffect } from "react";
interface RoleGuardProps {
children: ReactNode;
requiredRole: string;
fallback?: ReactNode;
}
export function RoleGuard({
children,
requiredRole,
fallback
}: RoleGuardProps) {
const { session, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !session) {
router.push("/login");
}
}, [session, loading, router]);
if (loading) {
return <div>Loading...</div>;
}
if (!session) {
return null;
}
// Check role from JWT claims (set in jwt callback)
const userRole = (session as any).role;
if (userRole !== requiredRole) {
return fallback || (
<div>
<h1>Access Denied</h1>
<p>You don't have permission to view this page.</p>
</div>
);
}
return <>{children}</>;
}
// Usage:
// <RoleGuard requiredRole="admin" fallback={<AccessDenied />}>
// <AdminPanel />
// </RoleGuard>Refetch session after profile update
'use client';
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useState } from "react";
export default function ProfileForm() {
const { session, refetch } = useAuth();
const [name, setName] = useState(session?.user.name || "");
const [saving, setSaving] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
try {
await fetch("/api/user/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
});
// Refetch session to get updated user data
await refetch();
alert("Profile updated!");
} catch (error) {
alert("Failed to update profile");
} finally {
setSaving(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="border rounded px-3 py-2"
/>
<button
type="submit"
disabled={saving}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{saving ? "Saving..." : "Save"}
</button>
</form>
);
}The hooks include full TypeScript support with type inference:
import { useAuth } from "@warpy-auth-sdk/core/hooks";
import type { Session } from "@warpy-auth-sdk/core";
function MyComponent() {
const { session, loading } = useAuth();
// session is typed as Session | null
// TypeScript knows session.user.email exists
if (session) {
const email: string = session.user.email; // ✓ Type-safe
}
}Extend the session type with custom fields:
import type { Session as BaseSession } from "@warpy-auth-sdk/core";
// Extend base session type
interface CustomSession extends BaseSession {
role: string;
subscriptionTier: "free" | "pro" | "enterprise";
}
// Use in component
function MyComponent() {
const { session } = useAuth();
const customSession = session as CustomSession | null;
if (customSession) {
// TypeScript knows about custom fields
const role = customSession.role;
const tier = customSession.subscriptionTier;
}
}Always check the loading state before rendering content:
const { session, loading } = useAuth();
if (loading) {
return <LoadingSpinner />;
}
// Now safe to use sessionRedirect unauthenticated users in useEffect:
useEffect(() => {
if (!loading && !session) {
router.push("/login");
}
}, [session, loading, router]);Never trust client-side authentication alone. Always validate server-side:
// Server Component or API Route
const session = await getServerSession();
if (!session) {
redirect("/login"); // or return 401
}Place AuthProvider as high as possible in your component tree:
// app/layout.tsx
<AuthProvider>
{children}
</AuthProvider>Use session data efficiently to avoid unnecessary re-renders:
// Extract only what you need
const { session } = useAuth();
const userName = session?.user.name;
// Instead of passing entire session
<Component name={userName} />Ensure AuthProvider is wrapping your component:
// ✗ Wrong
<MyComponent />
// ✓ Correct
<AuthProvider>
<MyComponent />
</AuthProvider>Call refetch() after actions that update the session:
const { refetch } = useAuth();
await updateUserProfile();
await refetch(); // Refresh sessionCheck loading state before redirecting:
// ✗ Wrong - infinite loop
useEffect(() => {
if (!session) {
router.push("/login");
}
}, [session]);
// ✓ Correct - check loading
useEffect(() => {
if (!loading && !session) {
router.push("/login");
}
}, [session, loading]);