Getting started
UI Integration Patterns
General UI integration patterns for Keyloom - server-side session reading, client components, authentication flows, and design patterns.
UI Integration Patterns
General UI integration patterns for building authentication interfaces with Keyloom. These patterns work with any UI library or custom components.
Prerequisites
- Keyloom handler configured at
/api/auth/[...keyloom] - React components or Next.js pages
- Optional: UI library (Tailwind CSS, Chakra UI, etc.)
Server-side session reading
Layout with session
import { getServerSession } from "@keyloom/nextjs";
import config from "@/keyloom.config";
import { NavBar } from "@/components/NavBar";
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession(config);
return (
<html>
<body>
<NavBar user={session?.user} />
<main>{children}</main>
</body>
</html>
);
}Protected page
import { getServerSession } from "@keyloom/nextjs";
import { redirect } from "next/navigation";
import config from "@/keyloom.config";
export default async function DashboardPage() {
const session = await getServerSession(config);
if (!session) {
redirect("/auth/signin");
}
return (
<div>
<h1>Welcome, {session.user.name}!</h1>
<p>Email: {session.user.email}</p>
</div>
);
}API route with session
import { getServerSession } from "@keyloom/nextjs";
import config from "@/keyloom.config";
export async function GET() {
const session = await getServerSession(config);
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return Response.json({
user: session.user,
sessionId: session.id,
});
}Authentication UI patterns
Sign-in page
import { SignInForm } from "@/components/SignInForm";
export default function SignInPage() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="text-3xl font-bold text-center">
Sign in to your account
</h2>
</div>
<SignInForm />
</div>
</div>
);
}Sign-in form component
export function SignInForm() {
return (
<div className="space-y-4">
<a
href="/api/auth/oauth/github/start?callbackUrl=/dashboard"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-900 hover:bg-gray-800"
>
Continue with GitHub
</a>
<a
href="/api/auth/oauth/google/start?callbackUrl=/dashboard"
className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
Continue with Google
</a>
<a
href="/api/auth/oauth/microsoft/start?callbackUrl=/dashboard"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
>
Continue with Microsoft
</a>
</div>
);
}Navigation bar
import type { User } from "@keyloom/core";
interface NavBarProps {
user?: User | null;
}
export function NavBar({ user }: NavBarProps) {
return (
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<h1 className="text-xl font-semibold">My App</h1>
</div>
<div className="flex items-center space-x-4">
{user ? (
<>
<span>Welcome, {user.name}</span>
<a
href="/api/auth/signout?callbackUrl=/"
className="text-gray-500 hover:text-gray-700"
>
Sign out
</a>
</>
) : (
<a
href="/auth/signin"
className="text-blue-600 hover:text-blue-800"
>
Sign in
</a>
)}
</div>
</div>
</div>
</nav>
);
}Client-side components
Profile component with hooks
"use client";
import { useSession } from "@keyloom/react";
export function Profile() {
const { data: session, status } = useSession();
if (status === "loading") {
return <div className="animate-pulse">Loading...</div>;
}
if (status === "unauthenticated") {
return (
<div className="text-center">
<p>Please sign in to view your profile</p>
<a href="/auth/signin" className="text-blue-600 hover:underline">
Sign in
</a>
</div>
);
}
return (
<div className="bg-white shadow rounded-lg p-6">
<div className="flex items-center space-x-4">
{session.user.image && (
<img
src={session.user.image}
alt="Profile"
className="w-16 h-16 rounded-full"
/>
)}
<div>
<h2 className="text-xl font-semibold">{session.user.name}</h2>
<p className="text-gray-600">{session.user.email}</p>
</div>
</div>
</div>
);
}Organization switcher (RBAC)
"use client";
import { useRBAC } from "@keyloom/react";
export function OrgSwitcher() {
const { activeOrg, organizations, switchOrg } = useRBAC();
if (!organizations?.length) return null;
return (
<div className="relative">
<select
value={activeOrg?.id || ""}
onChange={(e) => switchOrg(e.target.value)}
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
{organizations.map((org) => (
<option key={org.id} value={org.id}>
{org.name}
</option>
))}
</select>
</div>
);
}Design patterns
Loading states
export function LoadingSpinner() {
return (
<div className="flex justify-center items-center p-4">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
);
}Error boundaries
"use client";
import { useEffect, useState } from "react";
interface AuthErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ReactNode;
}
export function AuthErrorBoundary({
children,
fallback,
}: AuthErrorBoundaryProps) {
const [hasError, setHasError] = useState(false);
useEffect(() => {
const handleError = (event: ErrorEvent) => {
if (event.error?.code?.includes("AUTH")) {
setHasError(true);
}
};
window.addEventListener("error", handleError);
return () => window.removeEventListener("error", handleError);
}, []);
if (hasError) {
return (
fallback || (
<div className="text-center p-8">
<h2 className="text-xl font-semibold text-red-600">
Authentication Error
</h2>
<p className="mt-2 text-gray-600">
Please try refreshing the page or signing in again.
</p>
<button
onClick={() => setHasError(false)}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Retry
</button>
</div>
)
);
}
return <>{children}</>;
}Performance tips
- Use server-side session reading for initial page loads
- Implement proper loading states for better UX
- Cache user profile data appropriately
- Use React.memo for expensive components
Accessibility
- Include proper ARIA labels on authentication buttons
- Ensure keyboard navigation works
- Provide screen reader friendly loading states
- Use semantic HTML elements
See also
- UI Components: /docs/ui/overview
- React Hooks: /docs/react/hooks
- Next.js Integration: /docs/nextjs/overview
- Security: /docs/security/overview
Next steps
- Build your authentication UI components
- Implement proper error handling and loading states
- Add responsive design for mobile users
- Test accessibility with screen readers
How is this guide?