Clerk Auth
Authentication logic for Next.js App Router, built on top of Clerk.
No UI is included — every function returns data or an AuthResult, and your
components decide how to render it. Client and Server Components are both
covered, with a clear split between what runs where.
Dependencies
@clerk/nextjs: official Clerk SDK for Next.js. Provides the client hooks (useUser,useClerk,useSignIn,useSignUp), the server helpers (auth(),currentUser(),clerkClient()), the middleware helper (clerkMiddleware(),createRouteMatcher()), and the webhook verification helper (verifyWebhook()) that this module wraps. No other package is required.
Folder structure
auth/clerk/
├── types.ts // Shared types: AppUser, AppSession, Role, Permission, AuthResult, AuthError
├── service.ts // Internal server logic against the Clerk SDK — not imported directly from components
├── actions.ts // Public server-side API ("use server") — Server Components & Server Actions import from here
├── client.ts // Browser-only functions: sign out, update password, resend verification email
├── hooks.ts // Client-side React hooks: sign-up/sign-in/reset-password flows, OAuth, current user
├── middleware.ts // Route protection — copy to the project root, not inside this folder
├── webhooks/
│ └── route.ts // Clerk webhook handler — destination: app/api/webhooks/clerk/route.ts
├── env.example // Environment variables this module needs
└── setup.md // Usage guide + manual dashboard configuration steps
How the files fit together
service.tsis internal. It talks directly to the Clerk SDK (auth(),currentUser(),clerkClient()) and maps Clerk's objects into this module's normalizedAppUser/AppSessionshapes. Nothing outsideactions.tsshould import from it.actions.tsis the public server-side entry point. It's a"use server"file — every export is a thin async wrapper aroundservice.ts, callable directly from Server Components, Server Actions bound to forms/buttons, or Route Handlers.client.tsholds plain browser-only functions that take the Clerk objects they need (auser, asignOutfunction) as parameters, instead of calling hooks themselves — this keeps them callable from anywhere client-side, including fromhooks.ts.hooks.tswraps Clerk'suseSignIn/useSignUp/useUser/useClerkinto small state machines (step-based) so consuming components never need to know Clerk's internal status vocabulary. This is where sign-up, sign-in, OAuth, and password reset actually live.
What actions.ts covers
- Get the current authenticated user (
getCurrentUser) - Get the current session (
getCurrentSession) - Guard a Server Component/Action behind authentication, redirecting if signed
out (
requireAuth) - Guard behind a specific role (
requireRole) — extension point for when more than the single"user"role is needed - Check whether a role grants a permission (
checkPermission) - Permanently delete the current user's account via the Clerk Backend API
(
deleteAccount)
Sign-up, sign-in, OAuth, password reset, password change, and email
verification are client-driven (Clerk requires an active browser session for
these), so they live in hooks.ts/client.ts instead of actions.ts.
Usage examples
Protect a Server Component:
import { requireAuth } from "@/lib/auth/clerk/actions";
export default async function DashboardPage() {
const user = await requireAuth(); // redirects to sign-in if unauthenticated
return <p>Welcome, {user.firstName}</p>;
}
Sign in from a Client Component:
"use client";
import { useSignInFlow } from "@/lib/auth/clerk/hooks";
export function SignInForm() {
const { submit, isLoading } = useSignInFlow();
async function handleSubmit(formData: FormData) {
const result = await submit(
formData.get("email") as string,
formData.get("password") as string
);
if (!result.success) {
// show result.error.message
}
}
return <form action={handleSubmit}>{/* fields */}</form>;
}
Sign up with email verification:
"use client";
import { useSignUpFlow } from "@/lib/auth/clerk/hooks";
export function SignUpForm() {
const { step, submit, verifyEmail } = useSignUpFlow();
// step === "form" -> render email/password fields, call submit()
// step === "verify_email" -> render code input, call verifyEmail(code)
// step === "complete" -> user is signed in
}
Sign in with Google, GitHub, or Facebook:
"use client";
import { useOAuthSignIn } from "@/lib/auth/clerk/hooks";
export function OAuthButtons() {
const { signInWithGoogle, signInWithGitHub, signInWithFacebook } = useOAuthSignIn();
return (
<>
<button onClick={signInWithGoogle}>Continue with Google</button>
<button onClick={signInWithGitHub}>Continue with GitHub</button>
<button onClick={signInWithFacebook}>Continue with Facebook</button>
</>
);
}
Reset a forgotten password:
"use client";
import { useResetPasswordFlow } from "@/lib/auth/clerk/hooks";
export function ResetPasswordForm() {
const { step, requestReset, resetPassword } = useResetPasswordFlow();
// step === "request" -> ask for email, call requestReset(email)
// step === "reset" -> ask for code + new password, call resetPassword(code, newPassword)
// step === "complete" -> user is signed in with the new password
}
Delete the current account:
import { deleteAccount } from "@/lib/auth/clerk/actions";
async function handleDeleteAccount() {
"use server";
const result = await deleteAccount();
}
What you can build with this
This module is enough to build a complete authenticated app: gated
dashboards, a self-service account area (change password, delete account,
resend verification), social sign-up/sign-in with Google/GitHub/Facebook, and
a webhook-driven user lifecycle (default role stamped on user.created, with
user.deleted as the extension point for cleaning up your own database).
Role/Permission/requireRole ship scoped to a single "user" role today,
but are structured so adding an "admin" role later is a two-line change in
types.ts, not a rewrite.