Claude Skill

api-auth-clerk

Clerk managed authentication - ClerkProvider, middleware, pre-built components, hooks, server-side auth, organizations, webhooks

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_api-auth-clerk_skills_api-auth-clerk-3a51ef5.zip · 25 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/api-auth-clerk/skills/api-auth-clerk
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Clerk Authentication Patterns

Quick Guide: Clerk provides managed authentication with pre-built UI components, server-side helpers, and organization-based multi-tenancy. Use clerkMiddleware() for route protection, <Show> for conditional rendering, hooks for client state, and auth()/currentUser() for server-side auth. Clerk Core 3 (2026) replaces <SignedIn>/<SignedOut> with <Show>, renames the middleware file to proxy.ts (Next.js 16+), and consolidates packages.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use @clerk/nextjs/server for ALL server-side imports -- NEVER import server helpers from @clerk/nextjs)

(You MUST verify webhooks using Clerk's verifyWebhook helper -- NEVER trust unverified webhook payloads)

(You MUST use <Show> component instead of deprecated <SignedIn>/<SignedOut>/<Protect> -- these are removed in Core 3)

(You MUST NOT pass the full currentUser() object to the client -- it contains privateMetadata that must stay server-side)

(You MUST protect routes in BOTH middleware AND data access layer -- middleware alone is insufficient)

</critical_requirements>


Auto-detection: Clerk, ClerkProvider, clerkMiddleware, @clerk/nextjs, useUser, useAuth, useClerk, useSession, useOrganization, SignIn, SignUp, UserButton, UserProfile, OrganizationSwitcher, auth(), currentUser(), CLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, Show when="signed-in"

When to use:

  • Adding authentication and user management to an application
  • Building multi-tenant B2B apps with organization-based access control
  • Using pre-built sign-in/sign-up UI components with customizable theming
  • Protecting routes with middleware and server-side authorization checks
  • Syncing Clerk user data to your database via webhooks

Key patterns covered:

  • ClerkProvider setup, environment variables, middleware configuration
  • Pre-built UI components (<SignIn>, <SignUp>, <UserButton>, <Show>)
  • Client-side hooks (useUser, useAuth, useSession, useOrganization)
  • Server-side auth (auth(), currentUser()) in Server Components, Route Handlers, Server Actions
  • Middleware route protection with clerkMiddleware() and createRouteMatcher()
  • Organization-based multi-tenancy with roles and permissions
  • Webhook handling with Svix signature verification

When NOT to use:

  • Self-hosted auth requirement (need full control over auth data storage)
  • Cannot use a third-party auth service (compliance/regulatory constraints)
  • Simple API key authentication (custom middleware is sufficient)
  • Budget constraints prevent using a managed service

Detailed Resources:




<decision_framework>

Decision Framework

Client vs Server Auth

Where do you need auth data?
|-- Server Component, Route Handler, Server Action
|   |-- Need just userId/sessionId? --> auth()
|   |-- Need full user object? --> currentUser()
|   |-- Need to protect the route? --> auth.protect()
|   +-- Need org context? --> auth() returns orgId, orgRole
|
+-- Client Component (interactive UI)
    |-- Need user profile data? --> useUser()
    |-- Need session/token data? --> useAuth()
    |-- Need org data? --> useOrganization()
    +-- Need low-level Clerk API? --> useClerk()

Route Protection Strategy

What kind of route is it?
|-- Public (landing, sign-in, sign-up, webhooks)
|   +-- Add to isPublicRoute matcher, skip auth.protect()
|
|-- Authenticated (dashboard, profile, settings)
|   +-- auth.protect() in middleware + auth() check in data layer
|
+-- Authorized (admin, org-specific, permission-gated)
    |-- Role-based? --> auth.protect({ role: "org:admin" })
    +-- Permission-based? --> auth.protect((has) => has({ permission: "org:feature:action" }))

Component Choice

What auth UI do you need?
|-- Full sign-in page --> <SignIn /> on catch-all route
|-- Full sign-up page --> <SignUp /> on catch-all route
|-- Sign-in button (modal) --> <SignInButton />
|-- User avatar + menu --> <UserButton />
|-- Full profile editor --> <UserProfile />
|-- Org switcher --> <OrganizationSwitcher />
+-- Conditional content --> <Show when="signed-in"> or <Show when={{ role: "..." }}>

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Importing server helpers from @clerk/nextjs instead of @clerk/nextjs/server (breaks in Server Components)
  • Using deprecated <SignedIn>/<SignedOut>/<Protect> components (removed in Core 3)
  • Passing full currentUser() object to client components (leaks privateMetadata)
  • Trusting webhook payloads without verifyWebhook signature verification
  • Relying solely on middleware for route protection (middleware can be bypassed)
  • Importing types from @clerk/types instead of @clerk/shared/types (Core 3 rename)

Medium Priority Issues:

  • Not checking isLoaded before accessing hook data (causes hydration errors)
  • Using currentUser() on the client side (it is server-only)
  • Hardcoding Clerk keys instead of using environment variables
  • Using authMiddleware() (deprecated, replaced by clerkMiddleware())
  • Not making webhook endpoint public in middleware matcher
  • Using CLERK_WEBHOOK_SECRET instead of CLERK_WEBHOOK_SIGNING_SECRET

Common Mistakes:

  • Naming middleware file middleware.ts on Next.js 16+ (should be proxy.ts) or proxy.ts on Next.js <=15 (should be middleware.ts)
  • Forgetting catch-all segments [[...sign-in]] on sign-in/sign-up pages (breaks multi-step flows)
  • Using getToken() without try/catch in Core 3 (throws ClerkOfflineError when offline instead of returning null)
  • Not adding prefetch={false} to <Link> components pointing at protected routes from public pages

Gotchas & Edge Cases:

  • currentUser() counts against Backend API rate limits -- prefer useUser() hook on the client when possible
  • auth() in Server Components is deduplicated per request (safe to call multiple times)
  • <Show when={{ role: "org:admin" }}> requires an active organization in the session
  • Organization roles use the org: prefix (e.g., org:admin, org:member, org:billing)
  • Clerk Core 3 requires Node.js 20.9.0+, Next.js 15.2.3+
  • @clerk/clerk-react renamed to @clerk/react in Core 3 -- update imports after upgrade

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use @clerk/nextjs/server for ALL server-side imports -- NEVER import server helpers from @clerk/nextjs)

(You MUST verify webhooks using Clerk's verifyWebhook helper -- NEVER trust unverified webhook payloads)

(You MUST use <Show> component instead of deprecated <SignedIn>/<SignedOut>/<Protect> -- these are removed in Core 3)

(You MUST NOT pass the full currentUser() object to the client -- it contains privateMetadata that must stay server-side)

(You MUST protect routes in BOTH middleware AND data access layer -- middleware alone is insufficient)

Failure to follow these rules will create authentication vulnerabilities or break on Clerk Core 3.

</critical_reminders>

Files (skills)
  • examples
    • components.md 9.1 KB
      # Clerk Pre-Built Components Examples
      
      > UI components, customization, and the appearance prop. See [SKILL.md](../SKILL.md) for core concepts.
      
      **Core setup:** See [core.md](core.md). **Client hooks:** See [hooks.md](hooks.md). **Server auth:** See [server.md](server.md).
      
      ---
      
      ## Pattern 1: Show Component (Conditional Rendering)
      
      The `<Show>` component (Core 3) replaces deprecated `<SignedIn>`, `<SignedOut>`, and `<Protect>`.
      
      ### Good Example -- Auth-Aware Navigation
      
      ```tsx
      // components/nav-bar.tsx
      "use client";
      
      import {
        Show,
        SignInButton,
        SignUpButton,
        UserButton,
        OrganizationSwitcher,
      } from "@clerk/nextjs";
      import Link from "next/link";
      
      export function NavBar() {
        return (
          <nav className="nav-bar">
            <Link href="/">Home</Link>
      
            <Show when="signed-in">
              <Link href="/dashboard">Dashboard</Link>
              <OrganizationSwitcher />
              <UserButton />
            </Show>
      
            <Show when="signed-out">
              <SignInButton mode="modal" />
              <SignUpButton mode="modal" />
            </Show>
          </nav>
        );
      }
      ```
      
      **Why good:** `<Show>` for conditional rendering, modal mode opens sign-in overlay (no page navigation), `<UserButton>` provides profile/sign-out menu, named export
      
      ### Good Example -- Role-Based UI
      
      ```tsx
      // components/admin-nav.tsx
      "use client";
      
      import { Show } from "@clerk/nextjs";
      import Link from "next/link";
      
      export function AdminNav() {
        return (
          <nav>
            {/* Visible to all signed-in users */}
            <Show when="signed-in">
              <Link href="/dashboard">Dashboard</Link>
            </Show>
      
            {/* Visible only to org admins */}
            <Show when={{ role: "org:admin" }}>
              <Link href="/admin">Admin Panel</Link>
              <Link href="/admin/members">Manage Members</Link>
            </Show>
      
            {/* Permission-based rendering */}
            <Show when={{ permission: "org:billing:manage" }}>
              <Link href="/billing">Billing</Link>
            </Show>
      
            {/* Callback-based condition */}
            <Show
              when={(has) =>
                has({ role: "org:admin" }) || has({ permission: "org:reports:read" })
              }
              fallback={<p>You do not have access to reports.</p>}
            >
              <Link href="/reports">Reports</Link>
            </Show>
          </nav>
        );
      }
      ```
      
      **Why good:** Multiple `<Show>` conditions (string, object, callback), fallback for unauthorized users, combines role and permission checks
      
      ### Bad Example -- Using Deprecated Components
      
      ```tsx
      // BAD: Removed in Core 3
      import { SignedIn, SignedOut, Protect } from "@clerk/nextjs";
      
      export default function Nav() {
        return (
          <>
            <SignedIn>
              <Link href="/dashboard">Dashboard</Link>
            </SignedIn>
            <SignedOut>
              <SignInButton />
            </SignedOut>
            <Protect role="admin" fallback={<p>No access</p>}>
              <Link href="/admin">Admin</Link>
            </Protect>
          </>
        );
      }
      ```
      
      **Why bad:** `<SignedIn>`, `<SignedOut>`, `<Protect>` removed in Core 3, must migrate to `<Show>` with `when` prop
      
      ---
      
      ## Pattern 2: UserButton and UserProfile
      
      ### Good Example -- UserButton with Custom Menu Items
      
      ```tsx
      // components/user-menu.tsx
      "use client";
      
      import { UserButton } from "@clerk/nextjs";
      
      export function UserMenu() {
        return (
          <UserButton>
            <UserButton.MenuItems>
              <UserButton.Link
                label="My Orders"
                labelIcon={<ShoppingCartIcon />}
                href="/orders"
              />
              <UserButton.Link
                label="Billing"
                labelIcon={<CreditCardIcon />}
                href="/billing"
              />
              <UserButton.Action label="manageAccount" />
              <UserButton.Action label="signOut" />
            </UserButton.MenuItems>
          </UserButton>
        );
      }
      ```
      
      **Why good:** Custom menu items alongside built-in actions, `manageAccount` opens Clerk profile manager, type-safe built-in action labels
      
      ### Good Example -- UserProfile on Dedicated Page
      
      ```tsx
      // app/settings/[[...settings]]/page.tsx
      import { UserProfile } from "@clerk/nextjs";
      
      export function SettingsPage() {
        return (
          <main className="settings-page">
            <h1>Account Settings</h1>
            <UserProfile />
          </main>
        );
      }
      ```
      
      **Why good:** Catch-all route handles Clerk's multi-tab navigation, full profile management UI (name, email, password, security, connected accounts)
      
      ---
      
      ## Pattern 3: Appearance Prop Customization
      
      ### Good Example -- Global Theme via ClerkProvider
      
      ```tsx
      // app/layout.tsx
      import { ClerkProvider } from "@clerk/nextjs";
      import { dark } from "@clerk/themes";
      
      const BRAND_COLOR = "#6366f1";
      const BORDER_RADIUS = "0.75rem";
      
      export function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <html lang="en">
            <body>
              <ClerkProvider
                appearance={{
                  baseTheme: dark,
                  variables: {
                    colorPrimary: BRAND_COLOR,
                    borderRadius: BORDER_RADIUS,
                    fontFamily: "Inter, sans-serif",
                  },
                  elements: {
                    // Target specific internal elements
                    formButtonPrimary: "bg-indigo-600 hover:bg-indigo-700 text-white",
                    card: "shadow-lg border border-gray-200",
                    headerTitle: "text-2xl font-bold",
                  },
                }}
              >
                {children}
              </ClerkProvider>
            </body>
          </html>
        );
      }
      ```
      
      **Why good:** Named constants for brand values, `variables` for broad theme changes, `elements` for specific component targeting, dark theme as base, applies to all Clerk components
      
      ### Good Example -- Per-Component Appearance Override
      
      ```tsx
      // app/sign-in/[[...sign-in]]/page.tsx
      import { SignIn } from "@clerk/nextjs";
      
      const SIGN_IN_APPEARANCE = {
        elements: {
          rootBox: "mx-auto max-w-md",
          card: "rounded-xl shadow-2xl",
          headerTitle: "text-3xl font-bold text-center",
          headerSubtitle: "text-gray-500 text-center",
          socialButtonsBlockButton: "rounded-lg border-2",
        },
      } as const;
      
      export function SignInPage() {
        return (
          <main className="flex min-h-screen items-center justify-center">
            <SignIn appearance={SIGN_IN_APPEARANCE} />
          </main>
        );
      }
      ```
      
      **Why good:** Per-component override (doesn't affect other Clerk components), extracted to named constant, `as const` for type safety, centered layout
      
      ### Good Example -- Stacking Multiple Themes
      
      ```tsx
      // app/layout.tsx
      import { ClerkProvider } from "@clerk/nextjs";
      import { dark, neobrutalism } from "@clerk/themes";
      
      export function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <html lang="en">
            <body>
              <ClerkProvider
                appearance={{
                  baseTheme: [dark, neobrutalism],
                  variables: {
                    colorPrimary: "#ff6b6b",
                  },
                }}
              >
                {children}
              </ClerkProvider>
            </body>
          </html>
        );
      }
      ```
      
      **Why good:** Themes stack in order (last wins for conflicts), custom variables applied on top, easy to swap themes
      
      ---
      
      ## Pattern 4: SignIn and SignUp with Routing
      
      ### Good Example -- Modal Mode
      
      ```tsx
      // components/auth-buttons.tsx
      "use client";
      
      import { SignInButton, SignUpButton } from "@clerk/nextjs";
      
      export function AuthButtons() {
        return (
          <div className="auth-buttons">
            <SignInButton mode="modal">
              <button className="btn btn-primary">Sign In</button>
            </SignInButton>
      
            <SignUpButton mode="modal">
              <button className="btn btn-secondary">Create Account</button>
            </SignUpButton>
          </div>
        );
      }
      ```
      
      **Why good:** Modal mode keeps user on current page, custom button styling by wrapping with your own element, no page navigation required
      
      ### Good Example -- Redirect Mode with Custom Routing
      
      ```tsx
      // components/auth-buttons.tsx
      "use client";
      
      import { SignInButton, SignUpButton } from "@clerk/nextjs";
      
      const SIGN_IN_REDIRECT = "/dashboard";
      const SIGN_UP_REDIRECT = "/onboarding";
      
      export function AuthButtons() {
        return (
          <div className="auth-buttons">
            <SignInButton mode="redirect" forceRedirectUrl={SIGN_IN_REDIRECT}>
              <button className="btn btn-primary">Sign In</button>
            </SignInButton>
      
            <SignUpButton mode="redirect" forceRedirectUrl={SIGN_UP_REDIRECT}>
              <button className="btn btn-secondary">Get Started</button>
            </SignUpButton>
          </div>
        );
      }
      ```
      
      **Why good:** Named constants for redirect URLs, redirect mode navigates to dedicated auth pages, `forceRedirectUrl` overrides default redirect
      
      ---
      
      ## Pattern 5: OrganizationSwitcher
      
      ### Good Example -- Switcher with Creation
      
      ```tsx
      // components/org-switcher.tsx
      "use client";
      
      import { OrganizationSwitcher } from "@clerk/nextjs";
      
      export function OrgSwitcher() {
        return (
          <OrganizationSwitcher
            hidePersonal
            afterCreateOrganizationUrl="/org/:slug"
            afterSelectOrganizationUrl="/org/:slug"
            appearance={{
              elements: {
                rootBox: "w-full",
                organizationSwitcherTrigger:
                  "w-full justify-between rounded-lg border p-2",
              },
            }}
          />
        );
      }
      ```
      
      **Why good:** `hidePersonal` removes personal workspace option (B2B apps), `:slug` placeholder in URLs auto-replaced with org slug, custom styling for full-width trigger
      
      ---
      
      _For client hooks, see [hooks.md](hooks.md). For server-side auth, see [server.md](server.md). For organizations, see [organizations.md](organizations.md)._
      
    • core.md 8.8 KB
      # Clerk Core Setup Examples
      
      > Provider setup, environment variables, and middleware configuration. See [SKILL.md](../SKILL.md) for core concepts.
      
      **UI components:** See [components.md](components.md). **Client hooks:** See [hooks.md](hooks.md). **Server auth:** See [server.md](server.md). **Organizations:** See [organizations.md](organizations.md).
      
      ---
      
      ## Pattern 1: Environment Variables
      
      ### Good Example -- Complete Environment Setup
      
      ```env
      # .env.local
      
      # Required: Clerk API keys (from Dashboard > Configure > API Keys)
      NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_abc123...
      CLERK_SECRET_KEY=sk_test_xyz789...
      
      # Optional: Custom auth page paths
      NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
      NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
      
      # Optional: Post-auth redirect destinations
      NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
      NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/onboarding
      
      # Optional: Webhook signing secret (from Dashboard > Webhooks > Endpoint)
      CLERK_WEBHOOK_SIGNING_SECRET=whsec_abc123...
      ```
      
      **Why good:** All keys sourced from environment variables, public keys prefixed with `NEXT_PUBLIC_`, secret keys server-only, webhook secret separate from API keys
      
      ### Bad Example -- Hardcoded Keys
      
      ```tsx
      // BAD: Keys in source code
      <ClerkProvider publishableKey="pk_test_abc123">{children}</ClerkProvider>
      ```
      
      **Why bad:** Secrets in source code get committed to version control, no environment separation between dev/staging/prod
      
      ---
      
      ## Pattern 2: ClerkProvider Setup
      
      ### Good Example -- Minimal Provider
      
      ```tsx
      // app/layout.tsx
      import { ClerkProvider } from "@clerk/nextjs";
      
      export function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <html lang="en">
            <body>
              <ClerkProvider>{children}</ClerkProvider>
            </body>
          </html>
        );
      }
      ```
      
      **Why good:** Provider inside `<body>` (not wrapping `<html>`), reads publishable key from env automatically, named export, minimal config
      
      ### Good Example -- Provider with Appearance and Localization
      
      ```tsx
      // app/layout.tsx
      import { ClerkProvider } from "@clerk/nextjs";
      import { dark } from "@clerk/themes";
      
      export function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <html lang="en">
            <body>
              <ClerkProvider
                appearance={{
                  baseTheme: dark,
                  variables: {
                    colorPrimary: "#3b82f6",
                    borderRadius: "0.5rem",
                  },
                }}
                localization={{
                  signIn: {
                    start: {
                      title: "Welcome back",
                      subtitle: "Sign in to your account",
                    },
                  },
                }}
              >
                {children}
              </ClerkProvider>
            </body>
          </html>
        );
      }
      ```
      
      **Why good:** Theme applied globally to all Clerk components, CSS variables for brand consistency, localization for custom copy
      
      ### Good Example -- Dynamic Provider for Client-Side Auth
      
      ```tsx
      // app/layout.tsx
      import { ClerkProvider } from "@clerk/nextjs";
      
      export function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <html lang="en">
            <body>
              <ClerkProvider dynamic>{children}</ClerkProvider>
            </body>
          </html>
        );
      }
      ```
      
      **Why good:** `dynamic` prop required when routes need runtime authentication access with Next.js static rendering
      
      ---
      
      ## Pattern 3: Middleware Configuration
      
      ### Good Example -- Protect All Routes Except Public
      
      ```ts
      // proxy.ts (Next.js 16+) or middleware.ts (Next.js <=15)
      import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
      
      const isPublicRoute = createRouteMatcher([
        "/",
        "/sign-in(.*)",
        "/sign-up(.*)",
        "/api/webhooks(.*)",
        "/about",
        "/pricing",
      ]);
      
      export default clerkMiddleware(async (auth, req) => {
        if (!isPublicRoute(req)) {
          await auth.protect();
        }
      });
      
      export const config = {
        matcher: [
          // Skip Next.js internals and all 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)(.*)",
        ],
      };
      ```
      
      **Why good:** Explicit public route list, everything else requires auth, webhook endpoints public (verified separately), standard matcher pattern excludes static assets
      
      ### Good Example -- Role-Based Route Protection
      
      ```ts
      // proxy.ts
      import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
      
      const isPublicRoute = createRouteMatcher([
        "/",
        "/sign-in(.*)",
        "/sign-up(.*)",
        "/api/webhooks(.*)",
      ]);
      
      const isAdminRoute = createRouteMatcher(["/admin(.*)"]);
      const isBillingRoute = createRouteMatcher(["/billing(.*)"]);
      
      export default clerkMiddleware(async (auth, req) => {
        // Admin routes: require org:admin role
        if (isAdminRoute(req)) {
          await auth.protect((has) => has({ role: "org:admin" }));
          return;
        }
      
        // Billing routes: require billing permission
        if (isBillingRoute(req)) {
          await auth.protect((has) => has({ permission: "org:billing:manage" }));
          return;
        }
      
        // All other non-public routes: require authentication
        if (!isPublicRoute(req)) {
          await auth.protect();
        }
      });
      
      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)(.*)",
        ],
      };
      ```
      
      **Why good:** Tiered protection (admin > billing > authenticated), role and permission checks at middleware level, early return after specific checks
      
      ### Good Example -- Combining with Other Middleware
      
      ```ts
      // proxy.ts
      import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
      
      // Import your other middleware (i18n, rate limiting, etc.)
      import { otherMiddleware } from "./lib/middleware";
      
      const isPublicRoute = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)"]);
      
      export default clerkMiddleware(async (auth, req) => {
        if (!isPublicRoute(req)) {
          await auth.protect();
        }
      
        // Chain other middleware after Clerk auth
        return otherMiddleware(req);
      });
      
      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)(.*)",
        ],
      };
      ```
      
      **Why good:** Clerk middleware wraps other middleware, auth runs first then additional middleware, return value from clerkMiddleware callback becomes the response
      
      ### Bad Example -- No Route Protection
      
      ```ts
      // BAD: clerkMiddleware with no protection
      import { clerkMiddleware } from "@clerk/nextjs/server";
      
      export default clerkMiddleware();
      
      export const config = {
        matcher: ["/((?!_next).*)", "/(api|trpc)(.*)"],
      };
      ```
      
      **Why bad:** `clerkMiddleware()` without callback leaves ALL routes public by default, auth data is available but routes are not protected
      
      ### Bad Example -- Using Deprecated authMiddleware
      
      ```ts
      // BAD: Deprecated API
      import { authMiddleware } from "@clerk/nextjs";
      
      export default authMiddleware({
        publicRoutes: ["/", "/sign-in", "/sign-up"],
      });
      ```
      
      **Why bad:** `authMiddleware` is deprecated and removed in recent versions, replaced by `clerkMiddleware` with `createRouteMatcher`
      
      ---
      
      ## Pattern 4: Sign-In and Sign-Up Pages
      
      ### Good Example -- Dedicated Auth Pages
      
      ```tsx
      // app/sign-in/[[...sign-in]]/page.tsx
      import { SignIn } from "@clerk/nextjs";
      
      export function SignInPage() {
        return (
          <main className="auth-page">
            <SignIn />
          </main>
        );
      }
      ```
      
      ```tsx
      // app/sign-up/[[...sign-up]]/page.tsx
      import { SignUp } from "@clerk/nextjs";
      
      export function SignUpPage() {
        return (
          <main className="auth-page">
            <SignUp />
          </main>
        );
      }
      ```
      
      **Why good:** Catch-all route `[[...sign-in]]` handles multi-step flows (MFA, OAuth callbacks), named exports, minimal wrapper lets Clerk handle the form
      
      ### Bad Example -- Missing Catch-All Segment
      
      ```tsx
      // BAD: app/sign-in/page.tsx (no catch-all)
      import { SignIn } from "@clerk/nextjs";
      
      export default function SignInPage() {
        return <SignIn />;
      }
      ```
      
      **Why bad:** Without `[[...sign-in]]` catch-all, multi-step auth flows (MFA verification, OAuth callbacks) return 404, default export
      
      ---
      
      ## Pattern 5: Debugging Middleware
      
      ### Good Example -- Debug Mode
      
      ```ts
      // proxy.ts
      import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
      
      const isPublicRoute = createRouteMatcher(["/"]);
      
      export default clerkMiddleware(
        async (auth, req) => {
          if (!isPublicRoute(req)) {
            await auth.protect();
          }
        },
        { debug: process.env.NODE_ENV === "development" },
      );
      
      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)(.*)",
        ],
      };
      ```
      
      **Why good:** Debug logging only in development, helps diagnose route protection and auth state issues, no performance impact in production
      
      ---
      
      _For pre-built components, see [components.md](components.md). For client hooks, see [hooks.md](hooks.md). For server-side auth, see [server.md](server.md)._
      
    • hooks.md 9.6 KB
      # Clerk Client-Side Hooks Examples
      
      > Client-side hooks, conditional rendering, and loading states. See [SKILL.md](../SKILL.md) for core concepts.
      
      **Core setup:** See [core.md](core.md). **Components:** See [components.md](components.md). **Server auth:** See [server.md](server.md).
      
      ---
      
      ## Pattern 1: useUser -- User Profile Data
      
      ### Good Example -- User Profile Display
      
      ```tsx
      // components/user-profile-card.tsx
      "use client";
      
      import { useUser } from "@clerk/nextjs";
      
      export function UserProfileCard() {
        const { isLoaded, isSignedIn, user } = useUser();
      
        if (!isLoaded) {
          return <div className="skeleton" aria-label="Loading user profile" />;
        }
      
        if (!isSignedIn) {
          return <p>Please sign in to view your profile.</p>;
        }
      
        const primaryEmail = user.emailAddresses.find(
          (e) => e.id === user.primaryEmailAddressId,
        )?.emailAddress;
      
        return (
          <div className="profile-card">
            <img src={user.imageUrl} alt={`${user.firstName}'s avatar`} />
            <h2>
              {user.firstName} {user.lastName}
            </h2>
            <p>{primaryEmail}</p>
            <p>Joined: {user.createdAt?.toLocaleDateString()}</p>
          </div>
        );
      }
      ```
      
      **Why good:** Checks `isLoaded` first (prevents undefined access), checks `isSignedIn` before accessing `user`, finds primary email from email addresses array, accessible loading state
      
      ### Good Example -- Update User Profile
      
      ```tsx
      // components/edit-name-form.tsx
      "use client";
      
      import { useUser } from "@clerk/nextjs";
      import { useState, type FormEvent } from "react";
      
      export function EditNameForm() {
        const { isLoaded, isSignedIn, user } = useUser();
        const [firstName, setFirstName] = useState("");
        const [lastName, setLastName] = useState("");
        const [saving, setSaving] = useState(false);
      
        if (!isLoaded || !isSignedIn) return null;
      
        async function handleSubmit(e: FormEvent) {
          e.preventDefault();
          setSaving(true);
          try {
            await user.update({ firstName, lastName });
          } finally {
            setSaving(false);
          }
        }
      
        return (
          <form onSubmit={handleSubmit}>
            <input
              value={firstName}
              onChange={(e) => setFirstName(e.target.value)}
              placeholder={user.firstName ?? "First name"}
            />
            <input
              value={lastName}
              onChange={(e) => setLastName(e.target.value)}
              placeholder={user.lastName ?? "Last name"}
            />
            <button type="submit" disabled={saving}>
              {saving ? "Saving..." : "Update Name"}
            </button>
          </form>
        );
      }
      ```
      
      **Why good:** `user.update()` method updates Clerk user data directly, loading state during save, placeholder shows current values, guards against unloaded state
      
      ### Bad Example -- No Loading Check
      
      ```tsx
      // BAD: Accessing user without guards
      "use client";
      import { useUser } from "@clerk/nextjs";
      
      export default function Profile() {
        const { user } = useUser();
        // user is undefined until isLoaded is true, null when signed out
        return <p>Hello {user.firstName}</p>; // Runtime error!
      }
      ```
      
      **Why bad:** `user` is `undefined` during initialization and `null` when signed out, accessing `.firstName` throws TypeError, no loading state for UX, default export
      
      ---
      
      ## Pattern 2: useAuth -- Session and Authorization
      
      ### Good Example -- Authenticated API Calls
      
      ```tsx
      // components/data-fetcher.tsx
      "use client";
      
      import { useAuth } from "@clerk/nextjs";
      import { useCallback, useEffect, useState } from "react";
      
      interface DashboardData {
        stats: { label: string; value: number }[];
      }
      
      export function DataFetcher() {
        const { isLoaded, isSignedIn, getToken } = useAuth();
        const [data, setData] = useState<DashboardData | null>(null);
        const [error, setError] = useState<string | null>(null);
      
        const fetchData = useCallback(async () => {
          try {
            const token = await getToken();
            const response = await fetch("/api/dashboard", {
              headers: { Authorization: `Bearer ${token}` },
            });
      
            if (!response.ok) {
              throw new Error(`HTTP ${response.status}`);
            }
      
            setData(await response.json());
          } catch (err) {
            // Core 3: getToken() throws ClerkOfflineError when offline
            setError(err instanceof Error ? err.message : "Failed to fetch");
          }
        }, [getToken]);
      
        useEffect(() => {
          if (isSignedIn) {
            fetchData();
          }
        }, [isSignedIn, fetchData]);
      
        if (!isLoaded) return <div>Loading auth...</div>;
        if (!isSignedIn) return <div>Sign in required</div>;
        if (error) return <div>Error: {error}</div>;
        if (!data) return <div>Loading data...</div>;
      
        return (
          <ul>
            {data.stats.map((stat) => (
              <li key={stat.label}>
                {stat.label}: {stat.value}
              </li>
            ))}
          </ul>
        );
      }
      ```
      
      **Why good:** `getToken()` provides JWT for API authentication, try/catch handles `ClerkOfflineError` (Core 3 behavior), sequential loading states, fetches only when signed in
      
      ### Good Example -- Authorization Check with has()
      
      ```tsx
      // components/admin-actions.tsx
      "use client";
      
      import { useAuth } from "@clerk/nextjs";
      
      export function AdminActions() {
        const { isLoaded, isSignedIn, has, orgRole } = useAuth();
      
        if (!isLoaded || !isSignedIn) return null;
      
        const isAdmin = has?.({ role: "org:admin" });
        const canManageBilling = has?.({ permission: "org:billing:manage" });
      
        return (
          <div className="admin-actions">
            <p>Current role: {orgRole ?? "No organization"}</p>
      
            {isAdmin && (
              <button onClick={() => window.location.assign("/admin")}>
                Admin Panel
              </button>
            )}
      
            {canManageBilling && (
              <button onClick={() => window.location.assign("/billing")}>
                Manage Billing
              </button>
            )}
          </div>
        );
      }
      ```
      
      **Why good:** `has()` checks roles and permissions client-side, optional chaining on `has?.()` (null when no active org), `orgRole` shows current role, named export
      
      ---
      
      ## Pattern 3: useSession -- Session Management
      
      ### Good Example -- Session Info Display
      
      ```tsx
      // components/session-info.tsx
      "use client";
      
      import { useSession } from "@clerk/nextjs";
      
      export function SessionInfo() {
        const { isLoaded, isSignedIn, session } = useSession();
      
        if (!isLoaded) return <div>Loading...</div>;
        if (!isSignedIn || !session) return null;
      
        return (
          <div className="session-info">
            <p>Session ID: {session.id}</p>
            <p>Last active: {session.lastActiveAt.toLocaleString()}</p>
            <p>Status: {session.status}</p>
            <p>Expires: {session.expireAt.toLocaleString()}</p>
          </div>
        );
      }
      ```
      
      **Why good:** Session object provides activity and expiration info, useful for session management UIs, guards against unloaded state
      
      ---
      
      ## Pattern 4: useOrganization -- Active Organization
      
      ### Good Example -- Organization Dashboard
      
      ```tsx
      // components/org-dashboard.tsx
      "use client";
      
      import { useOrganization } from "@clerk/nextjs";
      
      export function OrgDashboard() {
        const { isLoaded, organization, membership } = useOrganization();
      
        if (!isLoaded) return <div>Loading organization...</div>;
      
        if (!organization) {
          return (
            <div>
              <p>No active organization.</p>
              <p>Select or create an organization to continue.</p>
            </div>
          );
        }
      
        return (
          <div className="org-dashboard">
            <div className="org-header">
              <img src={organization.imageUrl} alt={organization.name} />
              <h1>{organization.name}</h1>
              <p>Slug: {organization.slug}</p>
              <p>Members: {organization.membersCount}</p>
            </div>
      
            <div className="membership-info">
              <p>Your role: {membership?.role}</p>
              <p>Joined: {membership?.createdAt?.toLocaleDateString()}</p>
            </div>
          </div>
        );
      }
      ```
      
      **Why good:** Handles no-org state (user might not have selected an org), `membership` contains user's role in active org, `organization` has org metadata
      
      ---
      
      ## Pattern 5: Combining Multiple Hooks
      
      ### Good Example -- Complete Dashboard Header
      
      ```tsx
      // components/dashboard-header.tsx
      "use client";
      
      import { useUser, useAuth, useOrganization } from "@clerk/nextjs";
      import { UserButton, OrganizationSwitcher } from "@clerk/nextjs";
      
      export function DashboardHeader() {
        const { isLoaded: userLoaded, user } = useUser();
        const { orgId } = useAuth();
        const { organization } = useOrganization();
      
        if (!userLoaded) {
          return <header className="dashboard-header skeleton" />;
        }
      
        return (
          <header className="dashboard-header">
            <div className="header-left">
              <h1>
                {orgId && organization
                  ? organization.name
                  : `${user?.firstName}'s Workspace`}
              </h1>
            </div>
      
            <div className="header-right">
              <OrganizationSwitcher hidePersonal={false} />
              <UserButton />
            </div>
          </header>
        );
      }
      ```
      
      **Why good:** Combines user, auth, and org hooks for complete header, graceful fallback to personal workspace when no org, `hidePersonal={false}` allows personal workspace option
      
      ---
      
      ## Pattern 6: Reverification for Sensitive Actions
      
      ### Good Example -- Reverify Before Dangerous Action
      
      ```tsx
      // components/delete-account.tsx
      "use client";
      
      import { useReverification, useUser } from "@clerk/nextjs";
      
      export function DeleteAccountButton() {
        const { user } = useUser();
      
        const deleteAccount = useReverification(async () => {
          // This callback only runs after the user re-authenticates
          await user?.delete();
          window.location.assign("/");
        });
      
        return (
          <button onClick={() => deleteAccount()} className="btn btn-danger">
            Delete My Account
          </button>
        );
      }
      ```
      
      **Why good:** `useReverification` prompts user to re-authenticate before executing, protects sensitive actions from session theft, callback pattern keeps code clean
      
      ---
      
      _For server-side auth, see [server.md](server.md). For organizations, see [organizations.md](organizations.md)._
      
    • organizations.md 15.4 KB
      # Clerk Organizations & Multi-Tenancy Examples
      
      > Organization management, role-based access, permissions, and multi-tenant patterns. See [SKILL.md](../SKILL.md) for core concepts.
      
      **Core setup:** See [core.md](core.md). **Server auth:** See [server.md](server.md). **Client hooks:** See [hooks.md](hooks.md).
      
      ---
      
      ## Pattern 1: Organization Setup and Switching
      
      ### Good Example -- Organization-Aware Layout
      
      ```tsx
      // app/(org)/layout.tsx
      import { auth } from "@clerk/nextjs/server";
      import { redirect } from "next/navigation";
      
      export default async function OrgLayout({
        children,
      }: {
        children: React.ReactNode;
      }) {
        const { orgId, isAuthenticated } = await auth();
      
        if (!isAuthenticated) {
          redirect("/sign-in");
        }
      
        // Require an active organization for all org routes
        if (!orgId) {
          redirect("/org-selection");
        }
      
        return <div className="org-layout">{children}</div>;
      }
      ```
      
      **Why good:** Layout-level org check ensures all nested routes have an active org, redirects to org selection when no org is active, defense in depth on top of middleware
      
      ### Good Example -- Organization Selection Page
      
      ```tsx
      // app/org-selection/page.tsx
      "use client";
      
      import { OrganizationList } from "@clerk/nextjs";
      
      export function OrgSelectionPage() {
        return (
          <main className="org-selection">
            <h1>Select an Organization</h1>
            <OrganizationList
              afterSelectOrganizationUrl="/dashboard"
              afterCreateOrganizationUrl="/dashboard"
            />
          </main>
        );
      }
      ```
      
      **Why good:** `<OrganizationList>` provides create + select UI, redirects to dashboard after selection/creation, simple page for org-required apps
      
      ### Good Example -- Organization Switcher in Sidebar
      
      ```tsx
      // components/sidebar.tsx
      "use client";
      
      import { OrganizationSwitcher } from "@clerk/nextjs";
      
      export function Sidebar() {
        return (
          <aside className="sidebar">
            <OrganizationSwitcher
              hidePersonal
              afterCreateOrganizationUrl="/dashboard"
              afterSelectOrganizationUrl="/dashboard"
              appearance={{
                elements: {
                  rootBox: "w-full",
                  organizationSwitcherTrigger:
                    "w-full rounded-lg border p-3 hover:bg-gray-50",
                },
              }}
            />
            <nav>{/* sidebar navigation */}</nav>
          </aside>
        );
      }
      ```
      
      **Why good:** `hidePersonal` for B2B apps (no personal workspace), redirect after org change reloads dashboard with new org context, full-width styling
      
      ---
      
      ## Pattern 2: Role-Based Access Control
      
      ### Good Example -- Middleware-Level RBAC
      
      ```ts
      // proxy.ts
      import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
      
      const isPublicRoute = createRouteMatcher([
        "/",
        "/sign-in(.*)",
        "/sign-up(.*)",
        "/api/webhooks(.*)",
      ]);
      
      const isAdminRoute = createRouteMatcher(["/admin(.*)", "/api/admin(.*)"]);
      
      const isMemberRoute = createRouteMatcher([
        "/dashboard(.*)",
        "/projects(.*)",
        "/api/projects(.*)",
      ]);
      
      export default clerkMiddleware(async (auth, req) => {
        // Admin routes: require org:admin role
        if (isAdminRoute(req)) {
          await auth.protect((has) => has({ role: "org:admin" }));
          return;
        }
      
        // Member routes: require any org membership (any role)
        if (isMemberRoute(req)) {
          await auth.protect();
          return;
        }
      
        // All other non-public routes: require authentication
        if (!isPublicRoute(req)) {
          await auth.protect();
        }
      });
      
      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)(.*)",
        ],
      };
      ```
      
      **Why good:** Tiered protection -- admin routes require admin role, member routes require auth, public routes open, early return prevents falling through to less restrictive checks
      
      ### Good Example -- Server Component RBAC
      
      ```tsx
      // app/admin/members/page.tsx
      import { auth } from "@clerk/nextjs/server";
      import { redirect } from "next/navigation";
      
      export default async function MembersPage() {
        const { orgId, has, isAuthenticated, redirectToSignIn } = await auth();
      
        if (!isAuthenticated) return redirectToSignIn();
      
        if (!orgId) redirect("/org-selection");
      
        // Server-side role check (defense in depth)
        if (!has({ role: "org:admin" })) {
          redirect("/dashboard");
        }
      
        const members = await db.query.orgMembers.findMany({
          where: eq(orgMembers.orgId, orgId),
        });
      
        return (
          <main>
            <h1>Organization Members</h1>
            <table>
              <thead>
                <tr>
                  <th>Name</th>
                  <th>Email</th>
                  <th>Role</th>
                </tr>
              </thead>
              <tbody>
                {members.map((member) => (
                  <tr key={member.id}>
                    <td>{member.name}</td>
                    <td>{member.email}</td>
                    <td>{member.role}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </main>
        );
      }
      ```
      
      **Why good:** Auth, org, and role checks at data layer (not just middleware), `orgId` scopes query, custom redirect for unauthorized (to dashboard, not 404)
      
      ---
      
      ## Pattern 3: Permission-Based Access Control
      
      ### Good Example -- Custom Permissions
      
      Custom permissions follow the format `org:<feature>:<action>`. Configure in Clerk Dashboard under Configure > Organizations > Roles.
      
      ```tsx
      // app/invoices/page.tsx
      import { auth } from "@clerk/nextjs/server";
      import { redirect } from "next/navigation";
      
      export default async function InvoicesPage() {
        const { orgId, has, isAuthenticated } = await auth();
      
        if (!isAuthenticated) redirect("/sign-in");
        if (!orgId) redirect("/org-selection");
      
        const canRead = has({ permission: "org:invoices:read" });
        const canCreate = has({ permission: "org:invoices:create" });
        const canDelete = has({ permission: "org:invoices:delete" });
      
        if (!canRead) redirect("/dashboard");
      
        const invoices = await db.query.invoices.findMany({
          where: eq(invoices.orgId, orgId),
        });
      
        return (
          <main>
            <h1>Invoices</h1>
      
            {canCreate && (
              <a href="/invoices/new" className="btn btn-primary">
                Create Invoice
              </a>
            )}
      
            <table>
              <thead>
                <tr>
                  <th>Invoice #</th>
                  <th>Amount</th>
                  <th>Status</th>
                  {canDelete && <th>Actions</th>}
                </tr>
              </thead>
              <tbody>
                {invoices.map((invoice) => (
                  <tr key={invoice.id}>
                    <td>{invoice.number}</td>
                    <td>${invoice.amount.toFixed(2)}</td>
                    <td>{invoice.status}</td>
                    {canDelete && (
                      <td>
                        <form action={deleteInvoice}>
                          <input type="hidden" name="id" value={invoice.id} />
                          <button type="submit" className="btn btn-danger">
                            Delete
                          </button>
                        </form>
                      </td>
                    )}
                  </tr>
                ))}
              </tbody>
            </table>
          </main>
        );
      }
      ```
      
      **Why good:** Granular permission checks (read/create/delete), UI adapts based on permissions, page-level read check prevents unauthorized access, action column only shown to users with delete permission
      
      ### Good Example -- Permission-Protected Server Action
      
      ```ts
      // app/actions/delete-invoice.ts
      "use server";
      
      import { auth } from "@clerk/nextjs/server";
      
      export async function deleteInvoice(formData: FormData) {
        const { userId, orgId, has } = await auth();
      
        if (!userId || !orgId) {
          throw new Error("Unauthorized");
        }
      
        if (!has({ permission: "org:invoices:delete" })) {
          throw new Error("Insufficient permissions");
        }
      
        const invoiceId = formData.get("id") as string;
      
        // Verify invoice belongs to this organization
        const invoice = await db.query.invoices.findFirst({
          where: and(eq(invoices.id, invoiceId), eq(invoices.orgId, orgId)),
        });
      
        if (!invoice) {
          throw new Error("Invoice not found");
        }
      
        await db.delete(invoices).where(eq(invoices.id, invoiceId));
      
        return { success: true };
      }
      ```
      
      **Why good:** Permission check in server action (defense in depth), verifies invoice belongs to active org (prevents cross-tenant access), `orgId` used for scoping
      
      ---
      
      ## Pattern 4: Client-Side Organization Management
      
      ### Good Example -- useOrganization Hook
      
      ```tsx
      // components/org-member-list.tsx
      "use client";
      
      import { useOrganization } from "@clerk/nextjs";
      
      export function OrgMemberList() {
        const { isLoaded, organization, membership, memberships } = useOrganization({
          memberships: {
            pageSize: 20,
            keepPreviousData: true,
          },
        });
      
        if (!isLoaded) return <div>Loading...</div>;
      
        if (!organization) {
          return <p>No active organization</p>;
        }
      
        const isAdmin = membership?.role === "org:admin";
      
        return (
          <div className="member-list">
            <h2>{organization.name} Members</h2>
            <p>{organization.membersCount} total members</p>
      
            <ul>
              {memberships?.data?.map((member) => (
                <li key={member.id}>
                  <span>{member.publicUserData.identifier}</span>
                  <span className="role-badge">{member.role}</span>
                  {isAdmin && member.role !== "org:admin" && (
                    <button
                      onClick={async () => {
                        await member.update({ role: "org:admin" });
                      }}
                    >
                      Promote to Admin
                    </button>
                  )}
                </li>
              ))}
            </ul>
      
            {memberships?.hasNextPage && (
              <button onClick={() => memberships.fetchNext()}>Load More</button>
            )}
          </div>
        );
      }
      ```
      
      **Why good:** `useOrganization` with memberships pagination, role check before showing promote button, `fetchNext()` for pagination, `publicUserData.identifier` is the member's email
      
      ### Good Example -- Create Organization
      
      ```tsx
      // components/create-org-form.tsx
      "use client";
      
      import { useOrganizationList } from "@clerk/nextjs";
      import { useState, type FormEvent } from "react";
      
      export function CreateOrgForm() {
        const { isLoaded, createOrganization, setActive } = useOrganizationList();
        const [name, setName] = useState("");
        const [creating, setCreating] = useState(false);
      
        if (!isLoaded) return null;
      
        async function handleSubmit(e: FormEvent) {
          e.preventDefault();
          if (!name.trim()) return;
      
          setCreating(true);
          try {
            const org = await createOrganization({ name });
            // Set the new org as active
            await setActive({ organization: org.id });
            // Redirect or update UI
            window.location.assign("/dashboard");
          } catch (err) {
            console.error("Failed to create organization:", err);
          } finally {
            setCreating(false);
          }
        }
      
        return (
          <form onSubmit={handleSubmit}>
            <input
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="Organization name"
              required
            />
            <button type="submit" disabled={creating}>
              {creating ? "Creating..." : "Create Organization"}
            </button>
          </form>
        );
      }
      ```
      
      **Why good:** `createOrganization` from `useOrganizationList`, `setActive` switches to new org immediately, loading state during creation, error handling
      
      ---
      
      ## Pattern 5: Organization-Scoped Data Patterns
      
      ### Good Example -- Database Schema with Organization Scoping
      
      ```sql
      -- Database schema for Clerk-synced tables (use your ORM of choice)
      
      -- Users table: synced via Clerk webhooks
      CREATE TABLE users (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        clerk_id TEXT NOT NULL UNIQUE,       -- Clerk user ID
        email TEXT NOT NULL,
        first_name TEXT,
        last_name TEXT,
        image_url TEXT,
        created_at TIMESTAMP DEFAULT NOW()
      );
      
      -- Organizations table: synced via Clerk webhooks
      CREATE TABLE organizations (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        clerk_org_id TEXT NOT NULL UNIQUE,   -- Clerk organization ID
        name TEXT NOT NULL,
        slug TEXT NOT NULL,
        image_url TEXT,
        created_at TIMESTAMP DEFAULT NOW()
      );
      
      -- Organization members: synced via Clerk webhooks
      CREATE TABLE org_members (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        org_id TEXT NOT NULL,                -- Clerk organization ID
        user_id TEXT NOT NULL,               -- Clerk user ID
        role TEXT NOT NULL,                   -- e.g., "org:admin", "org:member"
        created_at TIMESTAMP DEFAULT NOW()
      );
      
      -- Application data: scoped to organization
      CREATE TABLE projects (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        org_id TEXT NOT NULL,                -- Clerk organization ID (foreign key for tenant scoping)
        name TEXT NOT NULL,
        created_by TEXT NOT NULL,            -- Clerk user ID
        created_at TIMESTAMP DEFAULT NOW()
      );
      ```
      
      **Why good:** `clerkId`/`clerkOrgId` as foreign keys (synced via webhooks), all app data scoped to `orgId`, `createdBy` tracks user who created resource
      
      ### Good Example -- Organization-Scoped Data Access Layer
      
      ```ts
      // lib/data-access.ts
      import { auth } from "@clerk/nextjs/server";
      
      // Always verify auth at the data access layer
      export async function getProjects() {
        const { userId, orgId } = await auth();
      
        if (!userId || !orgId) {
          throw new Error(
            "Unauthorized: requires authenticated user with active org",
          );
        }
      
        return db.query.projects.findMany({
          where: eq(projects.orgId, orgId),
          orderBy: desc(projects.createdAt),
        });
      }
      
      export async function getProject(projectId: string) {
        const { userId, orgId } = await auth();
      
        if (!userId || !orgId) {
          throw new Error("Unauthorized");
        }
      
        const project = await db.query.projects.findFirst({
          where: and(
            eq(projects.id, projectId),
            eq(projects.orgId, orgId), // Prevents cross-tenant access
          ),
        });
      
        if (!project) {
          throw new Error("Project not found");
        }
      
        return project;
      }
      
      export async function createProject(name: string) {
        const { userId, orgId, has } = await auth();
      
        if (!userId || !orgId) {
          throw new Error("Unauthorized");
        }
      
        if (!has({ permission: "org:projects:create" })) {
          throw new Error("Insufficient permissions");
        }
      
        return db
          .insert(projects)
          .values({
            name,
            orgId,
            createdBy: userId,
          })
          .returning();
      }
      ```
      
      **Why good:** Auth verified at data layer (defense in depth), every query scoped to `orgId` (prevents cross-tenant data leaks), permission checks for mutations, throws on unauthorized
      
      ### Bad Example -- No Organization Scoping
      
      ```ts
      // BAD: No org scoping -- leaks data across tenants
      export async function getProjects() {
        return db.query.projects.findMany(); // Returns ALL projects!
      }
      
      // BAD: No ownership check
      export async function deleteProject(id: string) {
        await db.delete(projects).where(eq(projects.id, id)); // Any user can delete
      }
      ```
      
      **Why bad:** No org scoping means users see data from all organizations, no ownership check means any authenticated user can delete any project, cross-tenant data leak
      
      ---
      
      ## Pattern 6: Organization Profile Management
      
      ### Good Example -- Organization Settings Page
      
      ```tsx
      // app/org/settings/[[...settings]]/page.tsx
      import { OrganizationProfile } from "@clerk/nextjs";
      import { auth } from "@clerk/nextjs/server";
      import { redirect } from "next/navigation";
      
      export default async function OrgSettingsPage() {
        const { orgId, has } = await auth();
      
        if (!orgId) redirect("/org-selection");
      
        // Only admins can access org settings
        if (!has({ role: "org:admin" })) {
          redirect("/dashboard");
        }
      
        return (
          <main>
            <h1>Organization Settings</h1>
            <OrganizationProfile />
          </main>
        );
      }
      ```
      
      **Why good:** Catch-all route for multi-tab navigation, admin-only access check, `<OrganizationProfile>` provides member management, settings, and domain verification UI
      
      ---
      
      _For core setup, see [core.md](core.md). For webhook handling, see [server.md](server.md)._
      
    • server.md 12.7 KB
      # Clerk Server-Side Auth Examples
      
      > Server Components, API route protection, Server Actions, and webhook handling. See [SKILL.md](../SKILL.md) for core concepts.
      
      **Core setup:** See [core.md](core.md). **Components:** See [components.md](components.md). **Client hooks:** See [hooks.md](hooks.md).
      
      ---
      
      ## Pattern 1: auth() in Server Components
      
      `auth()` is lightweight -- it reads session claims from the request cookie without making an API call. Safe to call multiple times (deduplicated per request).
      
      ### Good Example -- Protected Page
      
      ```tsx
      // app/dashboard/page.tsx
      import { auth } from "@clerk/nextjs/server";
      
      export default async function DashboardPage() {
        const { userId, orgId, isAuthenticated, redirectToSignIn } = await auth();
      
        if (!isAuthenticated) {
          return redirectToSignIn();
        }
      
        // userId is guaranteed non-null after isAuthenticated check
        const dashboardData = await getDashboardData(userId, orgId);
      
        return (
          <main>
            <h1>Dashboard</h1>
            <p>User: {userId}</p>
            {orgId && <p>Organization: {orgId}</p>}
            <DashboardContent data={dashboardData} />
          </main>
        );
      }
      ```
      
      **Why good:** `auth()` is lightweight (no API call), `redirectToSignIn()` handles redirect, `orgId` may be null (user without active org), data fetched with user context
      
      ### Good Example -- Authorization with protect()
      
      ```tsx
      // app/admin/page.tsx
      import { auth } from "@clerk/nextjs/server";
      
      export default async function AdminPage() {
        // Redirects to sign-in if unauthenticated, returns 404 if unauthorized
        await auth.protect({ role: "org:admin" });
      
        return (
          <main>
            <h1>Admin Panel</h1>
            <p>Only org admins can see this page.</p>
          </main>
        );
      }
      ```
      
      **Why good:** `protect()` handles both authentication and authorization in one call, unauthenticated users redirected, unauthorized users get 404
      
      ### Good Example -- Permission-Based Check with has()
      
      ```tsx
      // app/billing/page.tsx
      import { auth } from "@clerk/nextjs/server";
      import { redirect } from "next/navigation";
      
      export default async function BillingPage() {
        const { has, isAuthenticated, redirectToSignIn } = await auth();
      
        if (!isAuthenticated) {
          return redirectToSignIn();
        }
      
        if (!has({ permission: "org:billing:manage" })) {
          redirect("/dashboard");
        }
      
        return (
          <main>
            <h1>Billing Management</h1>
            {/* billing content */}
          </main>
        );
      }
      ```
      
      **Why good:** `has()` for granular permission checks, custom redirect for unauthorized (instead of 404), separate auth and authorization checks for different handling
      
      ---
      
      ## Pattern 2: currentUser() in Server Components
      
      `currentUser()` makes a Backend API call to fetch the full user object. Use when you need user profile data server-side.
      
      ### Good Example -- Server-Side User Data
      
      ```tsx
      // app/profile/page.tsx
      import { currentUser } from "@clerk/nextjs/server";
      import { redirect } from "next/navigation";
      
      export default async function ProfilePage() {
        const user = await currentUser();
      
        if (!user) {
          redirect("/sign-in");
        }
      
        // IMPORTANT: Only pass specific fields to client components
        // currentUser() returns privateMetadata which must stay server-side
        const safeProfile = {
          firstName: user.firstName,
          lastName: user.lastName,
          imageUrl: user.imageUrl,
          email: user.emailAddresses.find((e) => e.id === user.primaryEmailAddressId)
            ?.emailAddress,
          createdAt: user.createdAt,
        };
      
        return (
          <main>
            <h1>
              {safeProfile.firstName} {safeProfile.lastName}
            </h1>
            <img src={safeProfile.imageUrl} alt="Profile" />
            <p>{safeProfile.email}</p>
            <p>
              Member since: {new Date(safeProfile.createdAt).toLocaleDateString()}
            </p>
      
            {/* Safe to pass filtered data to client components */}
            <ProfileActions user={safeProfile} />
          </main>
        );
      }
      ```
      
      **Why good:** Explicitly picks safe fields before passing to client, never passes full user object (contains `privateMetadata`), redirect for unauthenticated users
      
      ### Bad Example -- Passing Full User to Client
      
      ```tsx
      // BAD: Leaking privateMetadata to client
      import { currentUser } from "@clerk/nextjs/server";
      
      export default async function Page() {
        const user = await currentUser();
        // user.privateMetadata is exposed to the client!
        return <ClientComponent user={user} />;
      }
      ```
      
      **Why bad:** `currentUser()` includes `privateMetadata` (sensitive server-only data), passing full object to client component leaks it to the browser, security vulnerability
      
      ---
      
      ## Pattern 3: Route Handlers (API Routes)
      
      ### Good Example -- Protected API Route
      
      ```ts
      // app/api/user/profile/route.ts
      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 profile = await db.query.users.findFirst({
          where: eq(users.clerkId, userId),
        });
      
        if (!profile) {
          return NextResponse.json({ error: "Not found" }, { status: 404 });
        }
      
        return NextResponse.json(profile);
      }
      
      export async function PATCH(req: Request) {
        const { userId } = await auth();
      
        if (!userId) {
          return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }
      
        const body = await req.json();
      
        const updated = await db
          .update(users)
          .set({ name: body.name, bio: body.bio })
          .where(eq(users.clerkId, userId))
          .returning();
      
        return NextResponse.json(updated[0]);
      }
      ```
      
      **Why good:** Auth check in every handler (defense in depth, not just middleware), `userId` scopes all queries to current user, proper HTTP status codes, both GET and PATCH protected
      
      ### Good Example -- Organization-Scoped API Route
      
      ```ts
      // app/api/org/members/route.ts
      import { auth } from "@clerk/nextjs/server";
      import { NextResponse } from "next/server";
      
      export async function GET() {
        const { userId, orgId, has } = await auth();
      
        if (!userId) {
          return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }
      
        if (!orgId) {
          return NextResponse.json(
            { error: "No active organization" },
            { status: 400 },
          );
        }
      
        // Check permission for member management
        if (!has({ permission: "org:members:read" })) {
          return NextResponse.json({ error: "Forbidden" }, { status: 403 });
        }
      
        const members = await db.query.orgMembers.findMany({
          where: eq(orgMembers.orgId, orgId),
        });
      
        return NextResponse.json(members);
      }
      ```
      
      **Why good:** Checks auth, active org, AND permission separately, proper error status codes (401/400/403), `orgId` scopes query to active organization
      
      ---
      
      ## Pattern 4: Server Actions
      
      ### Good Example -- Protected Server Action
      
      ```ts
      // app/actions/update-profile.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;
        const bio = formData.get("bio") as string;
      
        // Update in your database
        await db.update(users).set({ name, bio }).where(eq(users.clerkId, userId));
      
        // Optionally update Clerk user metadata
        const user = await currentUser();
        if (user) {
          await user.update({
            publicMetadata: { bio },
          });
        }
      
        return { success: true };
      }
      ```
      
      **Why good:** Auth check inside server action (not relying on middleware alone), `userId` scopes mutation, updates both local DB and Clerk metadata, `"use server"` directive
      
      ### Good Example -- Organization-Scoped Server Action
      
      ```ts
      // app/actions/create-project.ts
      "use server";
      
      import { auth } from "@clerk/nextjs/server";
      
      export async function createProject(formData: FormData) {
        const { userId, orgId, has } = await auth();
      
        if (!userId) {
          throw new Error("Unauthorized");
        }
      
        if (!orgId) {
          throw new Error("No active organization");
        }
      
        if (!has({ permission: "org:projects:create" })) {
          throw new Error("Insufficient permissions");
        }
      
        const name = formData.get("name") as string;
      
        const project = await db.insert(projects).values({
          name,
          orgId,
          createdBy: userId,
        });
      
        return { success: true, projectId: project.id };
      }
      ```
      
      **Why good:** Checks auth, org, and permission in server action, `orgId` scopes created resource to organization, `createdBy` tracks who created it
      
      ---
      
      ## Pattern 5: Webhook Handling
      
      ### Good Example -- Complete Webhook Handler
      
      ```ts
      // app/api/webhooks/clerk/route.ts
      import { verifyWebhook } from "@clerk/nextjs/webhooks";
      import type { UserJSON, OrganizationJSON } from "@clerk/shared/types";
      
      export async function POST(req: Request) {
        let evt;
        try {
          evt = await verifyWebhook(req);
        } catch {
          return new Response("Webhook verification failed", { status: 400 });
        }
      
        switch (evt.type) {
          case "user.created": {
            const userData = evt.data as UserJSON;
            const primaryEmail = userData.email_addresses.find(
              (e) => e.id === userData.primary_email_address_id,
            )?.email_address;
      
            await db.insert(users).values({
              clerkId: userData.id,
              email: primaryEmail ?? "",
              firstName: userData.first_name,
              lastName: userData.last_name,
              imageUrl: userData.image_url,
            });
            break;
          }
      
          case "user.updated": {
            const userData = evt.data as UserJSON;
            const primaryEmail = userData.email_addresses.find(
              (e) => e.id === userData.primary_email_address_id,
            )?.email_address;
      
            await db
              .update(users)
              .set({
                email: primaryEmail ?? "",
                firstName: userData.first_name,
                lastName: userData.last_name,
                imageUrl: userData.image_url,
              })
              .where(eq(users.clerkId, userData.id));
            break;
          }
      
          case "user.deleted": {
            const { id } = evt.data;
            if (id) {
              await db.delete(users).where(eq(users.clerkId, id));
            }
            break;
          }
      
          case "organization.created": {
            const orgData = evt.data as OrganizationJSON;
            await db.insert(organizations).values({
              clerkOrgId: orgData.id,
              name: orgData.name,
              slug: orgData.slug,
              imageUrl: orgData.image_url,
            });
            break;
          }
      
          case "organizationMembership.created": {
            const memberData = evt.data;
            await db.insert(orgMembers).values({
              orgId: memberData.organization.id,
              userId: memberData.public_user_data.user_id,
              role: memberData.role,
            });
            break;
          }
      
          case "organizationMembership.deleted": {
            const memberData = evt.data;
            await db
              .delete(orgMembers)
              .where(
                and(
                  eq(orgMembers.orgId, memberData.organization.id),
                  eq(orgMembers.userId, memberData.public_user_data.user_id),
                ),
              );
            break;
          }
      
          default:
            // Unhandled event type -- return 200 to acknowledge receipt
            break;
        }
      
        return new Response("OK", { status: 200 });
      }
      ```
      
      **Why good:** `verifyWebhook` validates Svix signature, handles multiple event types, try/catch for verification failure, always returns 200 (Clerk retries non-2xx), separate handlers for users and organizations
      
      ### Bad Example -- Unverified Webhook
      
      ```ts
      // BAD: No signature verification
      export async function POST(req: Request) {
        const body = await req.json();
        // Anyone can send fake events to this endpoint!
        await db.insert(users).values({
          clerkId: body.data.id,
          email: body.data.email_addresses[0].email_address,
        });
        return new Response("OK");
      }
      ```
      
      **Why bad:** No signature verification, anyone can POST fake data, direct database insertion from untrusted input, major security vulnerability
      
      ---
      
      ## Pattern 6: Custom JWT Templates
      
      ### Good Example -- Fetching Custom Token for External API
      
      ```tsx
      // components/external-api-caller.tsx
      "use client";
      
      import { useAuth } from "@clerk/nextjs";
      
      const EXTERNAL_API_URL = "https://api.example.com/data";
      
      export function ExternalApiCaller() {
        const { getToken, isSignedIn } = useAuth();
      
        async function callExternalApi() {
          if (!isSignedIn) return;
      
          try {
            // Fetch a JWT using a custom template configured in Clerk Dashboard
            const token = await getToken({ template: "my-external-api" });
      
            const response = await fetch(EXTERNAL_API_URL, {
              headers: { Authorization: `Bearer ${token}` },
            });
      
            return response.json();
          } catch (err) {
            // Core 3: throws ClerkOfflineError when offline
            console.error("Failed to get token:", err);
          }
        }
      
        return (
          <button onClick={callExternalApi} disabled={!isSignedIn}>
            Fetch External Data
          </button>
        );
      }
      ```
      
      **Why good:** Custom JWT template for external services, named constant for URL, try/catch for offline handling, configured in Clerk Dashboard under JWT Templates
      
      ---
      
      _For organizations and multi-tenancy, see [organizations.md](organizations.md)._
      
  • reference.md 9.7 KB
    # Clerk Quick Reference
    
    > Environment variables, Clerk Dashboard setup, webhook events, and common configuration. See [SKILL.md](SKILL.md) for core concepts and [examples/](examples/) for code examples.
    
    ---
    
    ## Environment Variables
    
    | Variable                                          | Required     | Description                                         |
    | ------------------------------------------------- | ------------ | --------------------------------------------------- |
    | `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`               | Yes          | Public key from Clerk Dashboard (starts with `pk_`) |
    | `CLERK_SECRET_KEY`                                | Yes          | Secret key from Clerk Dashboard (starts with `sk_`) |
    | `NEXT_PUBLIC_CLERK_SIGN_IN_URL`                   | No           | Custom sign-in page path (default: Clerk hosted)    |
    | `NEXT_PUBLIC_CLERK_SIGN_UP_URL`                   | No           | Custom sign-up page path (default: Clerk hosted)    |
    | `NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL` | No           | Redirect after sign-in (default: `/`)               |
    | `NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL` | No           | Redirect after sign-up (default: `/`)               |
    | `CLERK_WEBHOOK_SIGNING_SECRET`                    | For webhooks | Webhook signing secret (starts with `whsec_`)       |
    
    **Keys are found in:** Clerk Dashboard > Configure > API Keys
    
    **Webhook secret is found in:** Clerk Dashboard > Webhooks > Select endpoint > Signing Secret
    
    ---
    
    ## Clerk Dashboard Setup Checklist
    
    1. Create application at [dashboard.clerk.com](https://dashboard.clerk.com)
    2. Configure authentication methods (email, social, phone)
    3. Copy API keys to `.env.local`
    4. Enable Organizations (if needed): Configure > Organizations
    5. Set up roles and permissions (if needed): Configure > Organizations > Roles
    6. Add webhook endpoint (if needed): Webhooks > Add Endpoint
    7. Configure redirect URLs: Configure > Paths
    
    ---
    
    ## Webhook Event Types
    
    ### User Events
    
    | Event          | Trigger              | Payload          |
    | -------------- | -------------------- | ---------------- |
    | `user.created` | New user signs up    | `UserJSON`       |
    | `user.updated` | User profile changes | `UserJSON`       |
    | `user.deleted` | User account deleted | `{ id: string }` |
    
    ### Session Events
    
    | Event             | Trigger         | Payload       |
    | ----------------- | --------------- | ------------- |
    | `session.created` | User signs in   | `SessionJSON` |
    | `session.ended`   | User signs out  | `SessionJSON` |
    | `session.removed` | Session revoked | `SessionJSON` |
    
    ### Organization Events
    
    | Event                             | Trigger              | Payload                      |
    | --------------------------------- | -------------------- | ---------------------------- |
    | `organization.created`            | New org created      | `OrganizationJSON`           |
    | `organization.updated`            | Org settings changed | `OrganizationJSON`           |
    | `organization.deleted`            | Org deleted          | `{ id: string }`             |
    | `organizationMembership.created`  | Member added         | `OrganizationMembershipJSON` |
    | `organizationMembership.updated`  | Member role changed  | `OrganizationMembershipJSON` |
    | `organizationMembership.deleted`  | Member removed       | `OrganizationMembershipJSON` |
    | `organizationInvitation.created`  | Invite sent          | `OrganizationInvitationJSON` |
    | `organizationInvitation.accepted` | Invite accepted      | `OrganizationInvitationJSON` |
    | `organizationInvitation.revoked`  | Invite revoked       | `OrganizationInvitationJSON` |
    
    ### Email & SMS Events
    
    | Event           | Trigger              | Payload     |
    | --------------- | -------------------- | ----------- |
    | `email.created` | Email sent via Clerk | `EmailJSON` |
    | `sms.created`   | SMS sent via Clerk   | `SMSJSON`   |
    
    ---
    
    ## Default Organization Roles
    
    | Role   | Slug         | Default Permissions                                                       |
    | ------ | ------------ | ------------------------------------------------------------------------- |
    | Admin  | `org:admin`  | All system permissions (manage members, manage org, manage billing, etc.) |
    | Member | `org:member` | Read members, read billing                                                |
    
    **Custom permissions format:** `org:<feature>:<action>` (e.g., `org:invoices:create`, `org:reports:read`)
    
    ---
    
    ## Hooks Quick Reference
    
    | Hook                    | Purpose               | Key Returns                                                        |
    | ----------------------- | --------------------- | ------------------------------------------------------------------ |
    | `useUser()`             | User profile data     | `isLoaded`, `isSignedIn`, `user`                                   |
    | `useAuth()`             | Auth state and tokens | `userId`, `sessionId`, `orgId`, `getToken()`, `has()`, `signOut()` |
    | `useClerk()`            | Low-level Clerk API   | Full Clerk instance                                                |
    | `useSession()`          | Current session info  | `isLoaded`, `isSignedIn`, `session`                                |
    | `useOrganization()`     | Active org data       | `organization`, `membership`, `isLoaded`                           |
    | `useOrganizationList()` | All user orgs         | `organizationList`, `isLoaded`, `createOrganization()`             |
    | `useSignIn()`           | Custom sign-in flows  | `signIn`, `setActive`, `isLoaded`                                  |
    | `useSignUp()`           | Custom sign-up flows  | `signUp`, `setActive`, `isLoaded`                                  |
    
    ---
    
    ## Server-Side Helpers Quick Reference
    
    | Helper                 | Import                   | Context                                           | Returns                                                                    |
    | ---------------------- | ------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------- |
    | `auth()`               | `@clerk/nextjs/server`   | Server Components, Route Handlers, Server Actions | `userId`, `sessionId`, `orgId`, `protect()`, `has()`, `redirectToSignIn()` |
    | `currentUser()`        | `@clerk/nextjs/server`   | Server Components, Route Handlers, Server Actions | Full `BackendUser` object or `null`                                        |
    | `clerkMiddleware()`    | `@clerk/nextjs/server`   | Middleware file                                   | Middleware handler                                                         |
    | `createRouteMatcher()` | `@clerk/nextjs/server`   | Middleware file                                   | Route matching function                                                    |
    | `verifyWebhook()`      | `@clerk/nextjs/webhooks` | Webhook Route Handlers                            | Verified webhook event                                                     |
    
    ---
    
    ## Component Quick Reference
    
    | Component                  | Purpose                   | Usage                               |
    | -------------------------- | ------------------------- | ----------------------------------- |
    | `<ClerkProvider>`          | Auth context wrapper      | Wrap app in layout.tsx              |
    | `<SignIn />`               | Full sign-in form         | Dedicated page with catch-all route |
    | `<SignUp />`               | Full sign-up form         | Dedicated page with catch-all route |
    | `<UserButton />`           | Avatar with dropdown menu | Header/nav bar                      |
    | `<UserProfile />`          | Full profile management   | Dedicated settings page             |
    | `<OrganizationSwitcher />` | Org picker + creator      | Header/sidebar                      |
    | `<OrganizationProfile />`  | Org settings management   | Org settings page                   |
    | `<OrganizationList />`     | List user's organizations | Org selection page                  |
    | `<Show>`                   | Conditional rendering     | Auth/role/permission gating         |
    | `<SignInButton />`         | Button that opens sign-in | Header when signed out              |
    | `<SignUpButton />`         | Button that opens sign-up | Header when signed out              |
    
    ---
    
    ## Middleware File Naming
    
    | Next.js Version | Filename        | Location               |
    | --------------- | --------------- | ---------------------- |
    | Next.js 16+     | `proxy.ts`      | Project root or `src/` |
    | Next.js <=15    | `middleware.ts` | Project root or `src/` |
    
    The code is identical in both cases -- only the filename differs.
    
    ---
    
    ## Core 3 Migration Cheat Sheet
    
    | Core 2 (Deprecated)               | Core 3 (Current)                        |
    | --------------------------------- | --------------------------------------- |
    | `<SignedIn>`                      | `<Show when="signed-in">`               |
    | `<SignedOut>`                     | `<Show when="signed-out">`              |
    | `<Protect role="admin">`          | `<Show when={{ role: "org:admin" }}>`   |
    | `<Protect permission="...">`      | `<Show when={{ permission: "..." }}>`   |
    | `<Protect condition={...}>`       | `<Show when={(has) => has(...)}>`       |
    | `@clerk/clerk-react`              | `@clerk/react`                          |
    | `@clerk/types`                    | `@clerk/shared/types`                   |
    | `appearance.layout`               | `appearance.options`                    |
    | `authMiddleware()`                | `clerkMiddleware()`                     |
    | `middleware.ts` (Next.js 16)      | `proxy.ts`                              |
    | `getToken()` returns null offline | `getToken()` throws `ClerkOfflineError` |
    
    ---
    
    ## Version Requirements (Core 3)
    
    - Node.js 20.9.0+
    - Next.js 15.2.3+
    - Expo SDK 53+
    
    Run `npx @clerk/upgrade` to automate migration.
    
  • SKILL.md 16.2 KB
    ---
    name: api-auth-clerk
    description: Clerk managed authentication - ClerkProvider, middleware, pre-built components, hooks, server-side auth, organizations, webhooks
    ---
    
    # Clerk Authentication Patterns
    
    > **Quick Guide:** Clerk provides managed authentication with pre-built UI components, server-side helpers, and organization-based multi-tenancy. Use `clerkMiddleware()` for route protection, `<Show>` for conditional rendering, hooks for client state, and `auth()`/`currentUser()` for server-side auth. Clerk Core 3 (2026) replaces `<SignedIn>`/`<SignedOut>` with `<Show>`, renames the middleware file to `proxy.ts` (Next.js 16+), and consolidates packages.
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST use `@clerk/nextjs/server` for ALL server-side imports -- NEVER import server helpers from `@clerk/nextjs`)**
    
    **(You MUST verify webhooks using Clerk's `verifyWebhook` helper -- NEVER trust unverified webhook payloads)**
    
    **(You MUST use `<Show>` component instead of deprecated `<SignedIn>`/`<SignedOut>`/`<Protect>` -- these are removed in Core 3)**
    
    **(You MUST NOT pass the full `currentUser()` object to the client -- it contains `privateMetadata` that must stay server-side)**
    
    **(You MUST protect routes in BOTH middleware AND data access layer -- middleware alone is insufficient)**
    
    </critical_requirements>
    
    ---
    
    **Auto-detection:** Clerk, ClerkProvider, clerkMiddleware, @clerk/nextjs, useUser, useAuth, useClerk, useSession, useOrganization, SignIn, SignUp, UserButton, UserProfile, OrganizationSwitcher, auth(), currentUser(), CLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, Show when="signed-in"
    
    **When to use:**
    
    - Adding authentication and user management to an application
    - Building multi-tenant B2B apps with organization-based access control
    - Using pre-built sign-in/sign-up UI components with customizable theming
    - Protecting routes with middleware and server-side authorization checks
    - Syncing Clerk user data to your database via webhooks
    
    **Key patterns covered:**
    
    - ClerkProvider setup, environment variables, middleware configuration
    - Pre-built UI components (`<SignIn>`, `<SignUp>`, `<UserButton>`, `<Show>`)
    - Client-side hooks (`useUser`, `useAuth`, `useSession`, `useOrganization`)
    - Server-side auth (`auth()`, `currentUser()`) in Server Components, Route Handlers, Server Actions
    - Middleware route protection with `clerkMiddleware()` and `createRouteMatcher()`
    - Organization-based multi-tenancy with roles and permissions
    - Webhook handling with Svix signature verification
    
    **When NOT to use:**
    
    - Self-hosted auth requirement (need full control over auth data storage)
    - Cannot use a third-party auth service (compliance/regulatory constraints)
    - Simple API key authentication (custom middleware is sufficient)
    - Budget constraints prevent using a managed service
    
    **Detailed Resources:**
    
    - [reference.md](reference.md) - Decision frameworks, hooks quick reference, Core 3 migration cheat sheet
    - [examples/core.md](examples/core.md) - ClerkProvider, environment variables, middleware configuration
    - [examples/components.md](examples/components.md) - Pre-built components, customization, appearance prop
    - [examples/hooks.md](examples/hooks.md) - useUser, useAuth, useSession, loading states, conditional rendering
    - [examples/server.md](examples/server.md) - Server Components, API routes, Server Actions, webhook handling
    - [examples/organizations.md](examples/organizations.md) - Organization management, roles, permissions, RBAC
    
    ---
    
    <philosophy>
    
    ## Philosophy
    
    Clerk is a **managed authentication platform** that handles the entire auth lifecycle: sign-up, sign-in, session management, user profiles, organizations, and MFA. Instead of building auth from scratch, you integrate Clerk's SDK and pre-built components.
    
    **Core principles:**
    
    1. **Defense in depth** -- Protect routes at the middleware layer AND verify auth at every data access point. Middleware alone is insufficient (CVE-2025-29927 demonstrated middleware bypass vulnerabilities).
    2. **Server-first auth** -- Use `auth()` and `currentUser()` in Server Components and Route Handlers. Only use client hooks (`useUser`, `useAuth`) when you need reactive client-side state.
    3. **Pre-built over custom** -- Use Clerk's `<SignIn>`, `<SignUp>`, `<UserButton>` components. Only build custom flows when the pre-built components genuinely cannot meet requirements.
    4. **Organizations for multi-tenancy** -- Use Clerk Organizations with roles and permissions for B2B apps. Do not build custom tenant systems on top of Clerk's user model.
    5. **Webhook-driven sync** -- Sync Clerk data to your database via webhooks, not by polling. Always verify webhook signatures with `verifyWebhook`.
    
    **When to use Clerk:**
    
    - You need auth quickly with minimal custom code
    - You want pre-built UI components for sign-in/sign-up/user management
    - You need organization-based multi-tenancy with RBAC
    - You want managed MFA, SSO (SAML/OIDC), and social login
    
    **When NOT to use Clerk:**
    
    - You need full control over auth data storage (self-hosted requirement)
    - You cannot use a third-party auth service (compliance/regulatory)
    - Your app only needs simple API key authentication
    - Budget constraints prevent using a managed service
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: ClerkProvider and Middleware Setup
    
    Every Clerk app needs `<ClerkProvider>` wrapping the app and `clerkMiddleware()` protecting routes. See [examples/core.md](examples/core.md) for full setup examples.
    
    **Key rules:**
    
    - `ClerkProvider` goes inside `<body>`, not wrapping `<html>` -- Core 3 requires this
    - Use `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` env var, never hardcode keys
    - Middleware file is `proxy.ts` on Next.js 16+ or `middleware.ts` on Next.js <=15
    - Webhook endpoint must be in the public routes list (verified separately by `verifyWebhook`)
    - `CLERK_WEBHOOK_SIGNING_SECRET` is the env var for webhook signing (not `CLERK_WEBHOOK_SECRET`)
    
    ```ts
    // proxy.ts (Next.js 16+) or middleware.ts (Next.js <=15)
    const isPublicRoute = createRouteMatcher([
      "/",
      "/sign-in(.*)",
      "/sign-up(.*)",
      "/api/webhooks(.*)",
    ]);
    
    export default clerkMiddleware(async (auth, req) => {
      if (!isPublicRoute(req)) {
        await auth.protect();
      }
    });
    ```
    
    **Why good:** Public routes explicitly whitelisted, everything else requires auth, webhook endpoint is public (verified separately)
    
    ```ts
    // BAD: clerkMiddleware() with no callback
    export default clerkMiddleware(); // All routes are PUBLIC by default!
    ```
    
    **Why bad:** `clerkMiddleware()` without callback attaches auth data but does not protect any route
    
    ---
    
    ### Pattern 2: Pre-Built UI Components and `<Show>`
    
    Use `<Show>` for conditional rendering (Core 3 replacement for `<SignedIn>`/`<SignedOut>`/`<Protect>`). See [examples/components.md](examples/components.md) for full examples.
    
    ```tsx
    <Show when="signed-out"><SignInButton /></Show>
    <Show when="signed-in"><UserButton /></Show>
    <Show when={{ role: "org:admin" }}><AdminPanel /></Show>
    <Show when={(has) => has({ permission: "org:invoices:manage" })}><InvoiceManager /></Show>
    ```
    
    **Why good:** `<Show>` is the Core 3 API, supports string/object/callback conditions, `fallback` prop for unauthorized
    
    ```tsx
    // BAD: Deprecated -- removed in Core 3
    <SignedIn>...</SignedIn>
    <SignedOut>...</SignedOut>
    <Protect role="admin">...</Protect>
    ```
    
    **Why bad:** `<SignedIn>`, `<SignedOut>`, `<Protect>` removed in Core 3, use `<Show>` with `when` prop
    
    Sign-in/sign-up pages require catch-all route segments `[[...sign-in]]` for multi-step flows (MFA, OAuth callbacks).
    
    ---
    
    ### Pattern 3: Client-Side Hooks
    
    Use hooks in Client Components for reactive auth state. **Always check `isLoaded` before accessing data.** See [examples/hooks.md](examples/hooks.md) for complete examples.
    
    ```tsx
    "use client";
    const { isLoaded, isSignedIn, user } = useUser();
    if (!isLoaded) return <div>Loading...</div>;
    if (!isSignedIn) return <div>Please sign in</div>;
    // Now safe to access user.firstName, user.emailAddresses, etc.
    ```
    
    **Why good:** Prevents hydration errors from accessing `undefined` during init, prevents TypeError from accessing `null` when signed out
    
    ```tsx
    // BAD: Accessing user without guards
    const { user } = useUser();
    return <p>{user.firstName}</p>; // Runtime error when isLoaded=false or isSignedIn=false
    ```
    
    **Why bad:** `user` is `undefined` until loaded and `null` when signed out
    
    **Key hooks:**
    
    - `useUser()` -- user profile data (name, email, avatar)
    - `useAuth()` -- session tokens (`getToken()`), `userId`, `orgId`, `has()` for authorization
    - `useOrganization()` -- active org data, membership, paginated members list
    - `useReverification()` -- re-authenticate before sensitive actions (Core 3)
    
    In Core 3, `getToken()` throws `ClerkOfflineError` when offline instead of returning null -- always wrap in try/catch.
    
    ---
    
    ### Pattern 4: Server-Side Authentication
    
    Use `auth()` and `currentUser()` from `@clerk/nextjs/server`. See [examples/server.md](examples/server.md) for complete examples.
    
    **`auth()` -- lightweight, no API call, reads session claims:**
    
    ```tsx
    import { auth } from "@clerk/nextjs/server";
    const { userId, orgId, isAuthenticated, redirectToSignIn } = await auth();
    if (!isAuthenticated) return redirectToSignIn();
    ```
    
    **`currentUser()` -- full user object, makes Backend API call:**
    
    ```tsx
    import { currentUser } from "@clerk/nextjs/server";
    const user = await currentUser();
    // CRITICAL: Pick safe fields before passing to client components
    const safeData = { firstName: user.firstName, imageUrl: user.imageUrl };
    ```
    
    **Why critical:** `currentUser()` returns `privateMetadata` that must never reach the client. Explicitly pick fields.
    
    **Defense in depth:** Auth MUST be checked in every Server Action and Route Handler, not just middleware:
    
    ```ts
    "use server";
    const { userId } = await auth();
    if (!userId) throw new Error("Unauthorized");
    // userId scopes all queries/mutations to current user
    ```
    
    ---
    
    ### Pattern 5: Authorization with Roles and Permissions
    
    Use `auth.protect()` or `has()` for granular access control. See [examples/organizations.md](examples/organizations.md) for RBAC patterns.
    
    ```ts
    // Middleware: role-based route protection
    await auth.protect((has) => has({ role: "org:admin" }));
    
    // Server Component: permission check
    const { has } = await auth();
    if (!has({ permission: "org:invoices:delete" })) redirect("/dashboard");
    
    // Client: conditional rendering by role
    <Show when={{ role: "org:admin" }}><AdminPanel /></Show>
    ```
    
    **Permission format:** `org:<feature>:<action>` (e.g., `org:invoices:create`, `org:reports:read`)
    
    **Default roles:** `org:admin` (all permissions), `org:member` (read-only). Custom roles configured in Clerk Dashboard.
    
    ---
    
    ### Pattern 6: Webhook Handling
    
    Use Clerk webhooks (via Svix) to sync user data to your database. See [examples/server.md](examples/server.md) for complete handler.
    
    ```ts
    import { verifyWebhook } from "@clerk/nextjs/webhooks";
    
    export async function POST(req: Request) {
      const evt = await verifyWebhook(req); // reads CLERK_WEBHOOK_SIGNING_SECRET env var
      // evt.type: "user.created" | "user.updated" | "user.deleted" | "organization.*" | ...
      // evt.data: UserJSON | OrganizationJSON | etc.
    }
    ```
    
    **Why good:** `verifyWebhook` validates Svix signature, always returns 200 to prevent retries, webhook route is public in middleware (verified by signature not by auth)
    
    ```ts
    // BAD: No signature verification
    const body = await req.json(); // Anyone can POST fake data!
    await db.insert(users).values(body.data); // Security vulnerability
    ```
    
    **Why bad:** No signature verification means anyone can send fake webhook events to your endpoint
    
    **Import types from `@clerk/shared/types` (Core 3):** `UserJSON`, `OrganizationJSON`, `OrganizationMembershipJSON`
    
    </patterns>
    
    ---
    
    <decision_framework>
    
    ## Decision Framework
    
    ### Client vs Server Auth
    
    ```
    Where do you need auth data?
    |-- Server Component, Route Handler, Server Action
    |   |-- Need just userId/sessionId? --> auth()
    |   |-- Need full user object? --> currentUser()
    |   |-- Need to protect the route? --> auth.protect()
    |   +-- Need org context? --> auth() returns orgId, orgRole
    |
    +-- Client Component (interactive UI)
        |-- Need user profile data? --> useUser()
        |-- Need session/token data? --> useAuth()
        |-- Need org data? --> useOrganization()
        +-- Need low-level Clerk API? --> useClerk()
    ```
    
    ### Route Protection Strategy
    
    ```
    What kind of route is it?
    |-- Public (landing, sign-in, sign-up, webhooks)
    |   +-- Add to isPublicRoute matcher, skip auth.protect()
    |
    |-- Authenticated (dashboard, profile, settings)
    |   +-- auth.protect() in middleware + auth() check in data layer
    |
    +-- Authorized (admin, org-specific, permission-gated)
        |-- Role-based? --> auth.protect({ role: "org:admin" })
        +-- Permission-based? --> auth.protect((has) => has({ permission: "org:feature:action" }))
    ```
    
    ### Component Choice
    
    ```
    What auth UI do you need?
    |-- Full sign-in page --> <SignIn /> on catch-all route
    |-- Full sign-up page --> <SignUp /> on catch-all route
    |-- Sign-in button (modal) --> <SignInButton />
    |-- User avatar + menu --> <UserButton />
    |-- Full profile editor --> <UserProfile />
    |-- Org switcher --> <OrganizationSwitcher />
    +-- Conditional content --> <Show when="signed-in"> or <Show when={{ role: "..." }}>
    ```
    
    </decision_framework>
    
    ---
    
    <red_flags>
    
    ## RED FLAGS
    
    **High Priority Issues:**
    
    - Importing server helpers from `@clerk/nextjs` instead of `@clerk/nextjs/server` (breaks in Server Components)
    - Using deprecated `<SignedIn>`/`<SignedOut>`/`<Protect>` components (removed in Core 3)
    - Passing full `currentUser()` object to client components (leaks `privateMetadata`)
    - Trusting webhook payloads without `verifyWebhook` signature verification
    - Relying solely on middleware for route protection (middleware can be bypassed)
    - Importing types from `@clerk/types` instead of `@clerk/shared/types` (Core 3 rename)
    
    **Medium Priority Issues:**
    
    - Not checking `isLoaded` before accessing hook data (causes hydration errors)
    - Using `currentUser()` on the client side (it is server-only)
    - Hardcoding Clerk keys instead of using environment variables
    - Using `authMiddleware()` (deprecated, replaced by `clerkMiddleware()`)
    - Not making webhook endpoint public in middleware matcher
    - Using `CLERK_WEBHOOK_SECRET` instead of `CLERK_WEBHOOK_SIGNING_SECRET`
    
    **Common Mistakes:**
    
    - Naming middleware file `middleware.ts` on Next.js 16+ (should be `proxy.ts`) or `proxy.ts` on Next.js <=15 (should be `middleware.ts`)
    - Forgetting catch-all segments `[[...sign-in]]` on sign-in/sign-up pages (breaks multi-step flows)
    - Using `getToken()` without try/catch in Core 3 (throws `ClerkOfflineError` when offline instead of returning null)
    - Not adding `prefetch={false}` to `<Link>` components pointing at protected routes from public pages
    
    **Gotchas & Edge Cases:**
    
    - `currentUser()` counts against Backend API rate limits -- prefer `useUser()` hook on the client when possible
    - `auth()` in Server Components is deduplicated per request (safe to call multiple times)
    - `<Show when={{ role: "org:admin" }}>` requires an active organization in the session
    - Organization roles use the `org:` prefix (e.g., `org:admin`, `org:member`, `org:billing`)
    - Clerk Core 3 requires Node.js 20.9.0+, Next.js 15.2.3+
    - `@clerk/clerk-react` renamed to `@clerk/react` in Core 3 -- update imports after upgrade
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## CRITICAL REMINDERS
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST use `@clerk/nextjs/server` for ALL server-side imports -- NEVER import server helpers from `@clerk/nextjs`)**
    
    **(You MUST verify webhooks using Clerk's `verifyWebhook` helper -- NEVER trust unverified webhook payloads)**
    
    **(You MUST use `<Show>` component instead of deprecated `<SignedIn>`/`<SignedOut>`/`<Protect>` -- these are removed in Core 3)**
    
    **(You MUST NOT pass the full `currentUser()` object to the client -- it contains `privateMetadata` that must stay server-side)**
    
    **(You MUST protect routes in BOTH middleware AND data access layer -- middleware alone is insufficient)**
    
    **Failure to follow these rules will create authentication vulnerabilities or break on Clerk Core 3.**
    
    </critical_reminders>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related