Implements authentication with Clerk including user management, protected routes, middleware, and React components. Use when adding authentication, managing users, protecting routes, or implementing sign-in/sign-up flows.
Adds Clerk authentication to Next.js apps with user management, route protection, and React components. Use when implementing sign-in/sign-up flows, protecting routes via middleware, or managing users with hooks like useUser() and useAuth().
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Complete authentication and user management platform for modern web applications.
Install:
npm install @clerk/nextjs
Environment variables:
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
// middleware.ts (or proxy.ts for Next.js 15+)
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
};
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
import {
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs';
export function Header() {
return (
<header className="flex justify-between items-center p-4">
<h1>My App</h1>
<div className="flex gap-4">
<SignedOut>
<SignInButton mode="modal" />
<SignUpButton mode="modal" />
</SignedOut>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</div>
</header>
);
}
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs';
export default function SignInPage() {
return (
<div className="flex justify-center items-center min-h-screen">
<SignIn
appearance={{
elements: {
rootBox: 'mx-auto',
card: 'shadow-xl',
},
}}
/>
</div>
);
}
// app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from '@clerk/nextjs';
export default function SignUpPage() {
return (
<div className="flex justify-center items-center min-h-screen">
<SignUp />
</div>
);
}
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/settings(.*)',
'/api/private(.*)',
]);
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/public(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) {
await auth.protect();
}
});
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isAdminRoute = createRouteMatcher(['/admin(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isAdminRoute(req)) {
await auth.protect((has) => {
return has({ role: 'org:admin' });
});
}
});
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect((has) => {
return has({ permission: 'org:billing:manage' });
});
}
});
'use client';
import { useUser } from '@clerk/nextjs';
export function Profile() {
const { isLoaded, isSignedIn, user } = useUser();
if (!isLoaded) {
return <div>Loading...</div>;
}
if (!isSignedIn) {
return <div>Please sign in</div>;
}
return (
<div>
<h1>Hello, {user.firstName}!</h1>
<p>Email: {user.primaryEmailAddress?.emailAddress}</p>
<img src={user.imageUrl} alt="Profile" className="w-16 h-16 rounded-full" />
</div>
);
}
'use client';
import { useAuth } from '@clerk/nextjs';
export function AuthInfo() {
const { isLoaded, userId, sessionId, getToken } = useAuth();
if (!isLoaded) {
return <div>Loading...</div>;
}
if (!userId) {
return <div>Not signed in</div>;
}
return (
<div>
<p>User ID: {userId}</p>
<p>Session ID: {sessionId}</p>
</div>
);
}
// Get token for API calls
async function fetchWithAuth() {
const { getToken } = useAuth();
const token = await getToken();
const res = await fetch('/api/protected', {
headers: {
Authorization: `Bearer ${token}`,
},
});
}
'use client';
import { useClerk } from '@clerk/nextjs';
export function CustomSignOut() {
const { signOut, openSignIn, openUserProfile } = useClerk();
return (
<div className="flex gap-2">
<button onClick={() => openSignIn()}>Sign In</button>
<button onClick={() => openUserProfile()}>Profile</button>
<button onClick={() => signOut()}>Sign Out</button>
</div>
);
}
'use client';
import { useOrganization } from '@clerk/nextjs';
export function OrgInfo() {
const { isLoaded, organization, membership } = useOrganization();
if (!isLoaded) return <div>Loading...</div>;
if (!organization) return <div>No organization selected</div>;
return (
<div>
<h2>{organization.name}</h2>
<p>Role: {membership?.role}</p>
<p>Members: {organization.membersCount}</p>
</div>
);
}
// app/dashboard/page.tsx
import { currentUser, auth } from '@clerk/nextjs/server';
export default async function DashboardPage() {
const user = await currentUser();
if (!user) {
return <div>Please sign in</div>;
}
return (
<div>
<h1>Welcome, {user.firstName}!</h1>
<p>Email: {user.emailAddresses[0].emailAddress}</p>
</div>
);
}
// Using auth() for session data
export default async function ProtectedPage() {
const { userId, sessionClaims } = await auth();
if (!userId) {
return <div>Unauthorized</div>;
}
return <div>User ID: {userId}</div>;
}
// app/api/user/route.ts
import { auth, currentUser } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const user = await currentUser();
return NextResponse.json({
id: userId,
email: user?.emailAddresses[0].emailAddress,
});
}
// app/actions.ts
'use server';
import { auth, currentUser } from '@clerk/nextjs/server';
export async function updateProfile(formData: FormData) {
const { userId } = await auth();
if (!userId) {
throw new Error('Unauthorized');
}
const name = formData.get('name') as string;
// Update user in database
await db.user.update({
where: { clerkId: userId },
data: { name },
});
return { success: true };
}
// Server-side: Update public metadata
import { clerkClient } from '@clerk/nextjs/server';
await clerkClient.users.updateUserMetadata(userId, {
publicMetadata: {
role: 'admin',
plan: 'premium',
},
});
// Only accessible on server
await clerkClient.users.updateUserMetadata(userId, {
privateMetadata: {
stripeCustomerId: 'cus_...',
internalNotes: 'VIP customer',
},
});
'use client';
import { useUser } from '@clerk/nextjs';
export function UpdatePreferences() {
const { user } = useUser();
async function updateTheme(theme: string) {
await user?.update({
unsafeMetadata: {
theme,
notifications: true,
},
});
}
return (
<button onClick={() => updateTheme('dark')}>
Set Dark Theme
</button>
);
}
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { WebhookEvent } from '@clerk/nextjs/server';
export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET!;
const headerPayload = headers();
const svix_id = headerPayload.get('svix-id');
const svix_timestamp = headerPayload.get('svix-timestamp');
const svix_signature = headerPayload.get('svix-signature');
if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response('Missing svix headers', { status: 400 });
}
const payload = await req.json();
const body = JSON.stringify(payload);
const wh = new Webhook(WEBHOOK_SECRET);
let evt: WebhookEvent;
try {
evt = wh.verify(body, {
'svix-id': svix_id,
'svix-timestamp': svix_timestamp,
'svix-signature': svix_signature,
}) as WebhookEvent;
} catch (err) {
return new Response('Invalid signature', { status: 400 });
}
const eventType = evt.type;
if (eventType === 'user.created') {
const { id, email_addresses, first_name, last_name } = evt.data;
await db.user.create({
data: {
clerkId: id,
email: email_addresses[0].email_address,
firstName: first_name,
lastName: last_name,
},
});
}
if (eventType === 'user.updated') {
const { id, first_name, last_name } = evt.data;
await db.user.update({
where: { clerkId: id },
data: { firstName: first_name, lastName: last_name },
});
}
if (eventType === 'user.deleted') {
const { id } = evt.data;
await db.user.delete({
where: { clerkId: id },
});
}
return new Response('OK', { status: 200 });
}
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
import { dark } from '@clerk/themes';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider
appearance={{
baseTheme: dark,
variables: {
colorPrimary: '#3b82f6',
colorBackground: '#1f2937',
},
elements: {
card: 'shadow-xl rounded-xl',
formButtonPrimary: 'bg-blue-500 hover:bg-blue-600',
footerActionLink: 'text-blue-400 hover:text-blue-300',
},
}}
>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
| Mistake | Fix |
|---|---|
| Missing middleware | Add clerkMiddleware() |
| Unprotected API routes | Check auth() in routes |
| Client-side role checks only | Validate on server |
| Hardcoded redirect URLs | Use environment variables |
| Missing webhook verification | Always verify signatures |
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.