Usage & Integration
Practical examples of using Clerk authentication in Plainform components, pages, and API routes
Learn how to use Clerk authentication in your Plainform application with practical examples.
Server Components
Access user data in Server Components using auth() or currentUser():
import { auth, currentUser } from '@clerk/nextjs/server';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
// Get user ID only (faster)
const { userId } = await auth();
if (!userId) {
redirect('/sign-in');
}
// Get full user object when needed
const user = await currentUser();
return (
<div>
<h1>Welcome, {user.firstName}!</h1>
<p>Email: {user.emailAddresses[0].emailAddress}</p>
</div>
);
}Check Permissions
Verify user roles or permissions:
import { auth } from '@clerk/nextjs/server';
export default async function AdminPage() {
const { userId, has } = await auth();
if (!userId || !has({ role: 'admin' })) {
return <div>Access denied</div>;
}
return <div>Admin dashboard</div>;
}Client Components
Use Clerk hooks in Client Components for reactive auth state:
'use client';
import { useUser } from '@clerk/nextjs';
export function UserProfile() {
const { user, isLoaded, isSignedIn } = useUser();
if (!isLoaded) return <div>Loading...</div>;
if (!isSignedIn) return <div>Please sign in</div>;
return (
<div className="flex items-center gap-3">
<img
src={user.imageUrl}
alt={user.fullName || 'User'}
className="h-10 w-10 rounded-full"
/>
<div>
<p className="font-semibold">{user.fullName}</p>
<p className="text-sm text-muted-foreground">
{user.primaryEmailAddress?.emailAddress}
</p>
</div>
</div>
);
}Conditional Rendering
Show different content based on auth state:
'use client';
import { useAuth } from '@clerk/nextjs';
import Link from 'next/link';
export function AuthContent() {
const { isSignedIn, isLoaded } = useAuth();
if (!isLoaded) return <div>Loading...</div>;
return isSignedIn ? (
<Link href="/dashboard">Go to Dashboard</Link>
) : (
<div className="flex gap-4">
<Link href="/sign-up">Create Account</Link>
<Link href="/sign-in">Sign In</Link>
</div>
);
}API Routes
Protect API routes and verify authentication:
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId } = await auth();
if (!userId) {
return new NextResponse('Unauthorized', { status: 401 });
}
const userData = await getUserData(userId);
return NextResponse.json(userData);
}Clerk Webhook
Plainform also includes a webhook route at app/api/webhooks/clerk/route.ts.
It verifies Clerk webhook signatures and syncs user.created, user.updated,
and user.deleted events into the local User table.
Use the dedicated webhook page when setting up local tunneling, Clerk Dashboard
events, and the CLERK_WEBHOOK_SECRET:
Role-Based Access
Restrict API access by role:
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId, has } = await auth();
if (!userId || !has({ role: 'admin' })) {
return new NextResponse('Forbidden', { status: 403 });
}
const users = await getAllUsers();
return NextResponse.json(users);
}Server Actions
Verify authentication in Server Actions:
'use server';
import { auth } from '@clerk/nextjs/server';
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma/prisma';
export async function updateProfile(formData: FormData) {
const { userId } = await auth();
if (!userId) {
throw new Error('Unauthorized');
}
const name = formData.get('name') as string;
await prisma.user.update({
where: { clerkId: userId },
data: { name },
});
revalidatePath('/profile');
return { success: true };
}Custom Authentication Forms
Plainform uses custom sign-in and sign-up forms built with Clerk hooks for full design control. These are located in components/user/.
Plainform uses Clerk authentication hooks (useSignIn, useSignUp) with
custom UI components. No pre-built Clerk UI components are used for
authentication flows.
Sign-In Flow
The sign-in form uses the useSignIn hook:
'use client';
import { useSignIn } from '@clerk/nextjs';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
export function SignInForm() {
const { isLoaded, signIn, setActive } = useSignIn();
const router = useRouter();
const { register, handleSubmit, setError } = useForm();
const onSubmit = async (data) => {
const { identifier, password } = data;
if (!isLoaded) return;
try {
const signInAttempt = await signIn.create({
identifier,
password,
});
if (signInAttempt.status === 'complete') {
await setActive({ session: signInAttempt.createdSessionId });
router.push('/');
window.location.reload();
}
} catch (err) {
err.errors.forEach((error) => {
const field = error.meta?.paramName;
if (field && error.longMessage) {
setError(field, { type: 'manual', message: error.longMessage });
}
});
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('identifier')} type="email" placeholder="Email" />
<input {...register('password')} type="password" placeholder="Password" />
<button type="submit" disabled={isLoading}>
Sign In
</button>
</form>
);
}Key features:
signIn.create()- Authenticates with identifier and passwordsetActive()- Activates the returned Clerk sessionisLoaded- Prevents calls before Clerk has initialized- Clerk API errors - Mapped into React Hook Form field errors
Sign-Up Flow with Email Verification
The sign-up form uses useSignUp hook with email verification:
'use client';
import { useSignUp } from '@clerk/nextjs';
import { useForm } from 'react-hook-form';
export function SignUpForm() {
const [verifying, setVerifying] = useState(false);
const { isLoaded, signUp } = useSignUp();
const { register, handleSubmit, setError } = useForm();
const onSubmit = async (data) => {
const { email_address, password } = data;
if (!isLoaded) return;
try {
await signUp.create({
emailAddress: email_address,
password,
});
await signUp.prepareEmailAddressVerification({
strategy: 'email_code',
});
setVerifying(true);
} catch (err) {
err.errors.forEach((error) => {
const field = error.meta?.paramName;
if (field && error.longMessage) {
setError(field, { type: 'manual', message: error.longMessage });
}
});
}
};
if (verifying) return <SignUpVerificationForm />;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email_address')} type="email" placeholder="Email" />
<input {...register('password')} type="password" placeholder="Password" />
<button type="submit" disabled={isLoading}>
Sign Up
</button>
</form>
);
}Key features:
signUp.create()- Creates the account with email and passwordsignUp.prepareEmailAddressVerification()- Sends the email verification code- Local
verifyingstate - Switches from the sign-up form to the verification form - Clerk API errors - Mapped into React Hook Form field errors
Email Verification Form
The verification form handles email code verification:
'use client';
import { useSignUp } from '@clerk/nextjs';
import { useRouter } from 'next/navigation';
export function SignUpVerificationForm() {
const { isLoaded, signUp, setActive } = useSignUp();
const router = useRouter();
const onSubmit = async (data) => {
const { code } = data;
if (!isLoaded) return;
const signUpAttempt = await signUp.attemptEmailAddressVerification({
code,
});
if (signUpAttempt.status === 'complete') {
await setActive({ session: signUpAttempt.createdSessionId });
if (signUpAttempt.emailAddress) {
await addContact(signUpAttempt.emailAddress);
}
router.push('/');
}
};
const resendCode = async () => {
await signUp.prepareEmailAddressVerification({
strategy: 'email_code',
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="Enter 6-digit code" maxLength={6} />
<button type="submit" disabled={isLoading}>
Verify
</button>
<button type="button" onClick={resendCode}>
Resend Code
</button>
</form>
);
}Key features:
signUp.attemptEmailAddressVerification()- Verifies the email codesetActive()- Activates the created sessionaddContact()- Adds the verified email to the newsletter/contact listsignUp.prepareEmailAddressVerification()- Resends the verification code
Password Reset Flow
The password reset flow uses two forms:
1. Request Reset Code:
'use client';
import { useSignIn } from '@clerk/nextjs';
export function ForgotPasswordForm() {
const { isLoaded, signIn } = useSignIn();
const { handleSubmit, formState: { isSubmitting } } = useForm();
const sendResetCode = async (data) => {
const { identifier } = data;
if (!isLoaded) return;
await signIn.create({
strategy: 'reset_password_email_code',
identifier,
});
setCodeSent(true); // Show reset form
};
return (
<form onSubmit={handleSubmit(sendResetCode)}>
<input type="email" placeholder="Email" />
<button type="submit" disabled={isSubmitting}>
Send Reset Code
</button>
</form>
);
}2. Reset Password with Code:
'use client';
import { useSignIn } from '@clerk/nextjs';
import { useRouter } from 'next/navigation';
export function ResetPasswordForm() {
const { isLoaded, signIn, setActive } = useSignIn();
const router = useRouter();
const { handleSubmit, formState: { isSubmitting } } = useForm();
const onSubmit = async (data) => {
const { code, password } = data;
if (!isLoaded) return;
const resetPasswordAttempt = await signIn.attemptFirstFactor({
strategy: 'reset_password_email_code',
password,
code,
});
if (resetPasswordAttempt.status === 'complete') {
await setActive({ session: resetPasswordAttempt.createdSessionId });
router.push('/');
}
};
const resendCode = async () => {
await signIn.create({
strategy: 'reset_password_email_code',
identifier: signIn.identifier,
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="Enter 6-digit code" maxLength={6} />
<input type="password" placeholder="New password" />
<input type="password" placeholder="Confirm password" />
<button type="submit" disabled={isSubmitting}>
Reset Password
</button>
<button type="button" onClick={resendCode}>
Resend Code
</button>
</form>
);
}Key features:
signIn.create({ strategy: 'reset_password_email_code' })- Sends the reset codesignIn.attemptFirstFactor()- Verifies the code and submits the new passwordsetActive()- Activates the session after a successful password reset
OAuth Authentication
The OAuth connection component uses Clerk's redirect-based OAuth API. Here's the complete implementation:
'use client';
import { useSignIn, useSignUp } from '@clerk/nextjs';
import { OAuthStrategy } from '@clerk/types';
import { useState } from 'react';
interface IOAuthConnection {
isSignedUp?: boolean; // Optional: true on sign-up form
strategy: 'oauth_google' | 'oauth_github'; // Required: OAuth provider strategy
children: React.ReactNode; // Required: Button text content
icon: string; // Required: Icon name for the button
lastUsed?: string | null; // Optional: Last used strategy for badge
className?: string; // Optional: Additional CSS classes
}
export function OAuthConnection({
strategy,
children,
lastUsed,
icon,
isSignedUp,
}: IOAuthConnection) {
const { signIn } = useSignIn();
const { signUp } = useSignUp();
const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
async function signInWith(strategy: OAuthStrategy) {
setLoadingProvider(strategy);
return signIn?.authenticateWithRedirect({
strategy,
redirectUrl: '/sso-callback',
redirectUrlComplete: '/',
});
}
async function signUpWith(strategy: OAuthStrategy) {
setLoadingProvider(strategy);
return signUp?.authenticateWithRedirect({
strategy,
redirectUrl: '/sso-callback',
redirectUrlComplete: '/',
});
}
return (
<button
onClick={() => (!isSignedUp ? signInWith(strategy) : signUpWith(strategy))}
disabled={loadingProvider === strategy}
>
<SvgFinder icon={icon} />
{children}
{lastUsed === strategy && <span>Last used</span>}
</button>
);
}Usage example:
import { OAuthConnection } from '@/components/user/OAuthConnection';
import { useClerk } from '@clerk/nextjs';
export function SignInForm() {
const { client } = useClerk();
const lastStrategy = client?.lastAuthenticationStrategy;
return (
<div className="flex flex-col gap-4">
<OAuthConnection
strategy="oauth_google" // Required: Must match Clerk's strategy name
icon="Google" // Required: Icon name from SvgFinder
lastUsed={lastStrategy} // Optional: Shows "Last used" badge
>
Continue with Google {/* Required: Button text */}
</OAuthConnection>
<OAuthConnection
strategy="oauth_github"
icon="GitHub"
lastUsed={lastStrategy}
>
Continue with GitHub
</OAuthConnection>
</div>
);
}Required Props:
strategy: OAuth provider identifier (e.g.,"oauth_google","oauth_github","oauth_microsoft")- Must exactly match Clerk's strategy names
- See Clerk's documentation for all available providers
icon: Icon name to display on the button- Must exist in your
SvgFindercomponent
- Must exist in your
children: Button text content (e.g., "Continue with Google")
Optional Props:
lastUsed: Last authentication strategy used- Shows a "Last used" badge when it matches the current strategy
- Get from
useClerk().client?.lastAuthenticationStrategy
isSignedUp: Use this on sign-up forms so the component starts the sign-up OAuth flowclassName: Additional CSS classes for custom styling
Key features:
signIn.authenticateWithRedirect()- Initiates OAuth from the sign-in formsignUp.authenticateWithRedirect()- Initiates OAuth from the sign-up formloadingProvider- Local state to track which provider is currently loadingredirectUrl- Where the OAuth provider redirects after authentication (/sso-callback)redirectUrlComplete- Final destination after successful authentication (/)- Explicit flow selection -
isSignedUpswitches the button from sign-in OAuth to sign-up OAuth
The actual implementations in components/user/ include additional features
like form validation with Zod, styled UI components with Radix, BeatLoader spinner, and
comprehensive error handling with toast notifications. These examples show the core Clerk integration
patterns.
Pre-Built Components
User Profile
Use Clerk's <UserProfile> component for profile management:
import { UserProfile } from '@clerk/nextjs';
export default function ProfilePage() {
return (
<div className="flex justify-center p-8">
<UserProfile
appearance={{
elements: {
rootBox: 'w-full max-w-2xl',
card: 'shadow-lg',
},
}}
/>
</div>
);
}User Button
Add a user menu button to your navigation:
'use client';
import { UserButton } from '@clerk/nextjs';
export function Navigation() {
return (
<nav className="flex items-center justify-between p-4">
<div>Logo</div>
<UserButton />
</nav>
);
}Session Tokens
Access session tokens for external API calls:
'use client';
import { useAuth } from '@clerk/nextjs';
export function ApiCall() {
const { getToken } = useAuth();
const fetchData = async () => {
const token = await getToken();
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
};
return <button onClick={fetchData}>Fetch Data</button>;
}Testing Authentication
After implementing authentication:
- Sign Up: Navigate to
/sign-upand create a test account - Email Verification: Check email for verification code (or use
424242in dev mode) - Sign In: Sign in at
/sign-inwith your credentials - Protected Routes: Try accessing protected pages without auth
- Sign Out: Test sign-out functionality
In development, Clerk provides test mode. Use code 424242 for email
verification or check your terminal for magic links.
Next Steps
How is this guide ?
Last updated on