clerk-authentication

安装量: 75
排名: #10395

安装

npx skills add https://github.com/mindrally/skills --skill clerk-authentication

Clerk Authentication

You are an expert in Clerk authentication implementation for Next.js applications. Follow these guidelines when integrating Clerk.

Core Principles Implement defense-in-depth with multiple authentication layers Verify authentication at every data access point, not just middleware Protect server actions individually Use Clerk's built-in security features (HttpOnly cookies, CSRF protection) Installation and Setup npm install @clerk/nextjs

Environment Variables

Required

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_... CLERK_SECRET_KEY=sk_...

Optional: Custom URLs

NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard

Provider Setup App Router (app/layout.tsx) import { ClerkProvider } from '@clerk/nextjs';

export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body>{children}</body> </html> ); }

With Custom Appearance import { ClerkProvider } from '@clerk/nextjs'; import { dark } from '@clerk/themes';

export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ); }

Middleware Configuration Basic Middleware (middleware.ts) import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

// Define protected routes const isProtectedRoute = createRouteMatcher([ '/dashboard(.)', '/api/protected(.)', '/settings(.*)', ]);

// Define public routes (optional, for clarity) const isPublicRoute = createRouteMatcher([ '/', '/sign-in(.)', '/sign-up(.)', '/api/public(.*)', ]);

export default clerkMiddleware(async (auth, req) => { if (isProtectedRoute(req)) { await auth.protect(); } });

export const config = { matcher: [ // Skip Next.js internals and static files '/((?!_next|[^?]\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).)', // Always run for API routes '/(api|trpc)(.*)', ], };

Advanced Middleware with Role-Based Access import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isAdminRoute = createRouteMatcher(['/admin(.)']); const isProtectedRoute = createRouteMatcher(['/dashboard(.)', '/api/protected(.*)']);

export default clerkMiddleware(async (auth, req) => { const { userId, sessionClaims } = await auth();

// Admin routes require admin role if (isAdminRoute(req)) { if (!userId || sessionClaims?.metadata?.role !== 'admin') { return new Response('Forbidden', { status: 403 }); } }

// Protected routes require authentication if (isProtectedRoute(req)) { await auth.protect(); } });

Authentication in Server Components Using auth() import { auth } from '@clerk/nextjs/server';

export default async function DashboardPage() { const { userId } = await auth();

if (!userId) { redirect('/sign-in'); }

// Fetch user-specific data const data = await fetchUserData(userId);

return ; }

Using currentUser() import { currentUser } from '@clerk/nextjs/server';

export default async function ProfilePage() { const user = await currentUser();

if (!user) { redirect('/sign-in'); }

return (

Welcome, {user.firstName}!

Email: {user.emailAddresses[0]?.emailAddress}

); }

Authentication in Client Components useUser Hook 'use client';

import { useUser } from '@clerk/nextjs';

export function UserProfile() { const { isLoaded, isSignedIn, user } = useUser();

if (!isLoaded) { return ; }

if (!isSignedIn) { return ; }

return (

{user.fullName

{user.fullName}

); }

useAuth Hook 'use client';

import { useAuth } from '@clerk/nextjs';

export function ProtectedAction() { const { isLoaded, userId, getToken } = useAuth();

async function handleAction() { if (!userId) return;

// Get a fresh token for API calls
const token = await getToken();

const response = await fetch('/api/protected', {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

}

if (!isLoaded || !userId) { return null; }

return ; }

Server Actions Protection

Always protect server actions individually:

'use server';

import { auth } from '@clerk/nextjs/server';

export async function createPost(formData: FormData) { const { userId } = await auth();

if (!userId) { throw new Error('Unauthorized'); }

const title = formData.get('title') as string; const content = formData.get('content') as string;

// Create post with user ID const post = await db.post.create({ data: { title, content, authorId: userId, }, });

revalidatePath('/posts'); return post; }

With Role Validation 'use server';

import { auth } from '@clerk/nextjs/server';

export async function deleteUser(userId: string) { const { userId: currentUserId, sessionClaims } = await auth();

if (!currentUserId) { throw new Error('Unauthorized'); }

if (sessionClaims?.metadata?.role !== 'admin') { throw new Error('Forbidden: Admin access required'); }

await db.user.delete({ where: { id: userId } }); revalidatePath('/admin/users'); }

API Route Protection Route Handlers (App Router) import { auth } 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 data = await fetchUserData(userId); return NextResponse.json(data); }

export async function POST(request: Request) { const { userId } = await auth();

if (!userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); }

const body = await request.json(); // Process request...

return NextResponse.json({ success: true }); }

JWT Verification for External APIs import { auth } from '@clerk/nextjs/server';

export async function GET() { const { getToken } = await auth();

// Get JWT for external API const token = await getToken({ template: 'external-api' });

const response = await fetch('https://external-api.com/data', { headers: { Authorization: Bearer ${token}, }, });

return Response.json(await response.json()); }

Organization Support import { auth } from '@clerk/nextjs/server';

export async function getOrganizationData() { const { userId, orgId, orgRole } = await auth();

if (!userId || !orgId) { throw new Error('Must be in an organization'); }

// Check organization role if (orgRole !== 'org:admin') { throw new Error('Admin access required'); }

return await db.organization.findUnique({ where: { clerkOrgId: orgId }, }); }

Custom Session Claims Configure in Clerk Dashboard

Add custom claims via JWT Templates, then access them:

import { auth } from '@clerk/nextjs/server';

export async function checkSubscription() { const { sessionClaims } = await auth();

const plan = sessionClaims?.metadata?.subscriptionPlan;

if (plan !== 'pro') { throw new Error('Pro subscription required'); } }

UI Components Pre-built Components import { SignIn, SignUp, SignOutButton, UserButton, SignedIn, SignedOut, } from '@clerk/nextjs';

export function Header() { return (

); }

// Dedicated sign-in page export default function SignInPage() { return (

); }

Security Best Practices 1. Defense in Depth // Layer 1: Middleware export default clerkMiddleware(async (auth, req) => { if (isProtectedRoute(req)) { await auth.protect(); } });

// Layer 2: Server Component export default async function Page() { const { userId } = await auth(); if (!userId) redirect('/sign-in'); // ... }

// Layer 3: Data Access async function fetchUserData(userId: string) { const { userId: currentUserId } = await auth(); if (currentUserId !== userId) throw new Error('Forbidden'); // ... }

  1. Protect All Server Actions // Every server action should verify auth independently 'use server';

export async function sensitiveAction() { const { userId } = await auth(); if (!userId) throw new Error('Unauthorized'); // ... }

  1. Avoid Client-Side Only Protection // BAD: Client-side only check 'use client'; export function SecretComponent() { const { isSignedIn } = useAuth(); if (!isSignedIn) return null; return
    Secret Data
    ; // Data still sent to client! }

// GOOD: Server-side protection export default async function SecretPage() { const { userId } = await auth(); if (!userId) redirect('/sign-in'); const data = await fetchSecretData(userId); return ; }

Error Handling import { auth } from '@clerk/nextjs/server'; import { redirect } from 'next/navigation';

export default async function ProtectedPage() { try { const { userId } = await auth();

if (!userId) {
  redirect('/sign-in');
}

const data = await fetchUserData(userId);
return <Dashboard data={data} />;

} catch (error) { if (error instanceof AuthenticationError) { redirect('/sign-in'); } throw error; } }

Testing // Mock Clerk for testing import { auth } from '@clerk/nextjs/server';

jest.mock('@clerk/nextjs/server', () => ({ auth: jest.fn(), }));

describe('Protected API', () => { it('returns 401 for unauthenticated requests', async () => { (auth as jest.Mock).mockResolvedValue({ userId: null });

const response = await GET();
expect(response.status).toBe(401);

});

it('returns data for authenticated requests', async () => { (auth as jest.Mock).mockResolvedValue({ userId: 'user_123' });

const response = await GET();
expect(response.status).toBe(200);

}); });

Common Anti-Patterns to Avoid Relying solely on middleware for protection Not protecting server actions individually Using client-side auth checks for sensitive data Exposing user data without ownership verification Not validating organization membership for org-scoped resources Hardcoding role checks instead of using Clerk's RBAC Not handling loading states in client components

返回排行榜