.claude/skills/ts-clerk-auth/SKILL.md
Add authentication to web apps with Clerk — social login, email/password, magic links, organizations, RBAC, session management, webhooks, and multi-framework support. Use when tasks involve user authentication, team/org management, role-based access control, or integrating auth into Next.js, React, Remix, or Express applications.
npx skillsauth add eliferjunior/Claude clerk-authInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Drop-in authentication for modern web apps. Handles login UI, social providers, session management, organizations, and RBAC.
npm install @clerk/nextjs
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
// app/layout.tsx — Wrap app in ClerkProvider
import { ClerkProvider } from '@clerk/nextjs';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider>
<html><body>{children}</body></html>
</ClerkProvider>
);
}
// middleware.ts — Protect routes at the edge
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/pricing',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/webhooks(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
import { auth, currentUser } from '@clerk/nextjs/server';
export default async function Page() {
// Quick access to IDs and role
const { userId, orgId, orgRole } = await auth();
// Full user object when needed
const user = await currentUser();
return <p>Hello {user?.firstName}</p>;
}
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId, orgId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// ... fetch data scoped to orgId
}
'use server';
import { auth } from '@clerk/nextjs/server';
export async function createProject(name: string) {
const { userId, orgId, orgRole } = await auth();
if (!orgId || (orgRole !== 'org:admin' && orgRole !== 'org:owner')) {
throw new Error('Forbidden');
}
return db.projects.create({ data: { name, orgId, createdBy: userId } });
}
'use client';
import { useAuth, useUser, useOrganization } from '@clerk/nextjs';
export function ProfileCard() {
const { isSignedIn, userId } = useAuth();
const { user } = useUser();
const { organization, membership } = useOrganization();
if (!isSignedIn) return <p>Not signed in</p>;
return (
<div>
<p>{user?.fullName}</p>
<p>Org: {organization?.name}</p>
<p>Role: {membership?.role}</p>
</div>
);
}
import {
SignIn, // Full sign-in page
SignUp, // Full sign-up page
UserButton, // Avatar dropdown (profile, sign out)
UserProfile, // Full profile management page
OrganizationSwitcher, // Org dropdown + create org
OrganizationList, // List orgs + join/create
OrganizationProfile, // Org settings (members, roles)
} from '@clerk/nextjs';
// Sign-in page
// app/sign-in/[[...sign-in]]/page.tsx
export default function SignInPage() {
return <SignIn />;
}
// Header with org switcher and user menu
export function Header() {
return (
<nav>
<OrganizationSwitcher hidePersonal={true} />
<UserButton afterSignOutUrl="/" />
</nav>
);
}
Enable at dashboard.clerk.com → Organizations.
import { auth, clerkClient } from '@clerk/nextjs/server';
async function createOrg(name: string) {
const { userId } = await auth();
const client = await clerkClient();
return client.organizations.createOrganization({
name,
createdBy: userId!,
});
}
async function inviteMember(orgId: string, email: string, role: string) {
const client = await clerkClient();
return client.organizations.createOrganizationInvitation({
organizationId: orgId,
emailAddress: email,
role, // 'org:admin', 'org:member', or custom roles
inviterUserId: (await auth()).userId!,
});
}
Define at dashboard.clerk.com → Organizations → Roles:
org:owner — Full access, can delete org
org:admin — Manage members, settings
org:member — Standard access
org:viewer — Read-only (custom)
org:billing — Billing management only (custom)
Check roles in code:
const { orgRole, has } = await auth();
// Direct role check
if (orgRole === 'org:admin') { ... }
// Permission-based check (preferred — decouples code from role names)
if (has({ permission: 'org:projects:manage' })) { ... }
Sync Clerk events to your database:
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { WebhookEvent } from '@clerk/nextjs/server';
export async function POST(req: Request) {
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
const body = await req.text();
const svixHeaders = {
'svix-id': req.headers.get('svix-id')!,
'svix-timestamp': req.headers.get('svix-timestamp')!,
'svix-signature': req.headers.get('svix-signature')!,
};
const event = wh.verify(body, svixHeaders) as WebhookEvent;
switch (event.type) {
case 'user.created':
await db.users.create({ data: {
clerkId: event.data.id,
email: event.data.email_addresses[0]?.email_address,
name: `${event.data.first_name} ${event.data.last_name}`.trim(),
}});
break;
case 'user.deleted':
await db.users.delete({ where: { clerkId: event.data.id } });
break;
case 'organization.created':
await db.orgs.create({ data: {
clerkOrgId: event.data.id,
name: event.data.name,
slug: event.data.slug,
}});
break;
}
return new Response('OK');
}
Key events: user.created, user.updated, user.deleted, organization.created, organization.updated, organizationMembership.created, organizationMembership.deleted.
For external APIs or microservices that need to verify Clerk tokens:
// Configure at dashboard.clerk.com → JWT Templates
// Template name: "api-token"
// Claims: { "userId": "{{user.id}}", "orgId": "{{org.id}}", "role": "{{org.role}}" }
// Client: get a custom JWT
const { getToken } = useAuth();
const token = await getToken({ template: 'api-token' });
// External API: verify the JWT
import { createClerkClient } from '@clerk/backend';
const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY });
async function verifyRequest(req: Request) {
const token = req.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) throw new Error('No token');
return clerk.verifyToken(token);
}
import { ClerkExpressRequireAuth } from '@clerk/clerk-sdk-node';
// Protect routes
app.use('/api', ClerkExpressRequireAuth());
app.get('/api/me', (req, res) => {
res.json({ userId: req.auth.userId, orgId: req.auth.orgId });
});
auth() in server components, not useAuth() — server-side checks can't be bypassed by the clientsvix library to verify every webhook payloadhas({ permission: 'X' }) is more maintainable than role === 'org:admin'hidePersonal={true} for B2B apps — personal workspaces confuse users in team-based productsdevelopment
Expert guidance for Fireworks AI, the platform for running open-source LLMs (Llama, Mixtral, Qwen, etc.) with enterprise-grade speed and reliability. Helps developers integrate Fireworks' inference API, fine-tune models, and deploy custom model endpoints with function calling and structured output support.
development
Convert any website into clean, structured data with Firecrawl — API-first web scraping service. Use when someone asks to "turn a website into markdown", "scrape website for LLM", "Firecrawl", "extract website content as clean text", "crawl and convert to structured data", or "scrape website for RAG". Covers single-page scraping, full-site crawling, structured extraction, and LLM-ready output.
tools
Expert guidance for Firebase, Google's platform for building and scaling web and mobile applications. Helps developers set up authentication, Firestore/Realtime Database, Cloud Functions, hosting, storage, and analytics using Firebase's SDK and CLI.
development
When the user needs to build file upload functionality for a web application. Use when the user mentions "file upload," "image upload," "upload endpoint," "multipart upload," "presigned URL," "S3 upload," "file validation," "upload to cloud storage," or "accept user files." Handles upload endpoints, file validation (type, size, magic bytes), cloud storage integration, and upload status tracking. For image/video processing after upload, see media-transcoder.