Claude
Skills
Sign in
Back

authentication-authorization-clerk

Included with Lifetime
$97 forever

Implement secure authentication and authorization using Clerk. Use this skill when you need to authenticate users, protect routes, check permissions, implement subscription-based access control, or integrate Clerk with your application. Triggers include "authentication", "auth", "authorization", "Clerk", "protect route", "check user", "sign in", "session", "permissions", "subscription access".

General

What this skill does


# Authentication & Authorization with Clerk

## Why We Use Clerk

### The Authentication Problem

Building secure authentication from scratch requires:
- Password hashing (bcrypt/Argon2 with proper salts)
- Session management (secure cookies, expiration, renewal)
- Password reset flows (secure token generation, email verification)
- Account lockout (prevent brute force)
- MFA support (TOTP, SMS, authenticator apps)
- Social login (OAuth flows for Google, GitHub, etc.)
- User database sync
- Security best practices for all of the above

**Time to implement securely:** 2-4 weeks for experienced developers

**For vibe coders using AI:** High risk of security gaps

### Real-World Custom Auth Failures

**Ashley Madison Breach (2015):**
Custom authentication with weak password hashing. **32 million accounts** compromised.

**Dropbox Breach (2012):**
Custom authentication led to password hash database theft. **68 million accounts** affected.

According to Veracode's 2024 report, applications using managed authentication services (like Clerk, Auth0) had **73% fewer authentication-related vulnerabilities** than those with custom authentication.

## Our Clerk Architecture

### What Clerk Handles (So We Don't Have To)

- ✅ Password hashing (bcrypt/Argon2)
- ✅ Session management (secure cookies)
- ✅ MFA (built-in support)
- ✅ OAuth providers (Google, GitHub, etc.)
- ✅ Email verification
- ✅ Password reset flows
- ✅ Account lockout
- ✅ Security monitoring
- ✅ Compliance (SOC 2, GDPR)

**Clerk is SOC 2 certified:** This means an independent auditor verified their security controls meet industry standards. We inherit that certification.

## Implementation Files

- `middleware.ts` - Clerk authentication for protected routes
- `app/dashboard/*` - Protected by middleware
- Clerk manages its own session cookies

## Basic Authentication

### Server-Side Authentication (API Routes)

```typescript
import { auth } from '@clerk/nextjs/server';
import { handleUnauthorizedError } from '@/lib/errorHandler';

async function handler(request: NextRequest) {
  // Get current user
  const { userId } = await auth();

  if (!userId) {
    return handleUnauthorizedError('Authentication required');
  }

  // User is authenticated, proceed
  // Use userId to associate data with user
}
```

### Client-Side Authentication (Components)

```typescript
'use client';

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

export function ProfileComponent() {
  const { isLoaded, userId, sessionId } = useAuth();
  const { isLoaded: userLoaded, user } = useUser();

  if (!isLoaded || !userLoaded) {
    return <div>Loading...</div>;
  }

  if (!userId) {
    return <div>Please sign in</div>;
  }

  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <p>Email: {user.primaryEmailAddress?.emailAddress}</p>
    </div>
  );
}
```

### Protecting Routes with Middleware

```typescript
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

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

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

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
```

## Authorization Patterns

### Resource Ownership Verification

```typescript
// app/api/posts/[id]/route.ts
import { auth } from '@clerk/nextjs/server';
import { handleUnauthorizedError, handleForbiddenError } from '@/lib/errorHandler';

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const { userId } = await auth();
  if (!userId) {
    return handleUnauthorizedError();
  }

  // Get resource
  const post = await db.posts.findOne({ id: params.id });

  // Check ownership
  if (post.userId !== userId) {
    return handleForbiddenError('Only the post author can delete this post');
  }

  // User is authorized
  await db.posts.delete({ id: params.id });
  return NextResponse.json({ success: true });
}
```

### Role-Based Access Control (RBAC)

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

export async function handler(request: NextRequest) {
  const { userId, sessionClaims } = await auth();

  if (!userId) {
    return handleUnauthorizedError();
  }

  // Check role
  const role = sessionClaims?.metadata?.role as string;

  if (role !== 'admin') {
    return handleForbiddenError('Admin access required');
  }

  // User has admin role
  // Proceed with admin operation
}
```

### Subscription-Based Authorization

#### Server-Side (API Routes)

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

export async function handler(request: NextRequest) {
  const { userId, sessionClaims } = await auth();

  if (!userId) {
    return handleUnauthorizedError();
  }

  // Check subscription plan
  const plan = sessionClaims?.metadata?.plan as string;

  if (plan === 'free_user') {
    return NextResponse.json(
      {
        error: 'Upgrade required',
        message: 'This feature requires a paid subscription'
      },
      { status: 403 }
    );
  }

  // User has paid subscription
  // Proceed with premium feature
}
```

#### Client-Side (Components)

```typescript
'use client';

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

export function PremiumFeature() {
  return (
    <Protect
      condition={(has) => !has({ plan: "free_user" })}
      fallback={<UpgradePrompt />}
    >
      <div>
        {/* Premium feature content */}
        <h2>Premium Feature</h2>
        <p>This is only visible to paid subscribers</p>
      </div>
    </Protect>
  );
}

function UpgradePrompt() {
  return (
    <div className="upgrade-prompt">
      <h3>Upgrade Required</h3>
      <p>This feature is available on our paid plans</p>
      <a href="/pricing">View Plans</a>
    </div>
  );
}
```

## Complete Protected API Route Example

```typescript
// app/api/premium/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@clerk/nextjs/server';
import { withRateLimit } from '@/lib/withRateLimit';
import { withCsrf } from '@/lib/withCsrf';
import { validateRequest } from '@/lib/validateRequest';
import { safeTextSchema } from '@/lib/validation';
import {
  handleApiError,
  handleUnauthorizedError,
  handleForbiddenError
} from '@/lib/errorHandler';

async function generateHandler(request: NextRequest) {
  try {
    // 1. Authentication
    const { userId, sessionClaims } = await auth();
    if (!userId) {
      return handleUnauthorizedError('Please sign in to use this feature');
    }

    // 2. Authorization (subscription check)
    const plan = sessionClaims?.metadata?.plan as string;
    if (plan === 'free_user') {
      return handleForbiddenError('Premium subscription required');
    }

    // 3. Input validation
    const body = await request.json();
    const validation = validateRequest(safeTextSchema, body);
    if (!validation.success) {
      return validation.response;
    }

    const prompt = validation.data;

    // 4. Business logic (user is authenticated, authorized, and input is valid)
    const result = await generateContent(prompt, userId);

    return NextResponse.json({ result });

  } catch (error) {
    return handleApiError(error, 'premium-generate');
  }
}

export const POST = withRateLimit(withCsrf(generateHandler));

export const config = {
  runtime: 'nodejs',
};
```

## User Metadata & Custom Claims

### Storing User Metadata

Clerk allows you to store custom metadata with each user:

```typescript
import { clerkClient } from '@clerk/nextjs/server';

// Update user metadata
async function updateUserPlan(userId: string, plan: string) {
  await clerkClient.users.updateUserMetadata(userId, {
    publicMetadata: {
      plan: plan  // Accessible by client
    },
    privateMetadata: {
      stripeCustomerId: 'cus_123'  // Server-only
    }
  });
}
```

### Accessing Metadata

**Server-side:**
```typescript
const { se
Files: 2
Size: 22.3 KB
Complexity: 30/100
Category: General

Related in General