Communitygithub.com

Clerk 인증 패턴

ClerkProvider, 미들웨어, 사전 구축 컴포넌트, 훅, 서버 측 인증, 조직, 웹훅을 통한 관리형 인증

Clerk 인증 패턴란 무엇인가요?

이 스킬은 관리형 인증 플랫폼 Clerk을 애플리케이션에 통합하는 포괄적인 패턴을 제공합니다. ClerkProvider 설정, middleware()를 통한 라우트 보호, 사전 구축된 UI 컴포넌트 사용, 클라이언트 훅 및 서버 측 auth()/currentUser() 함수 활용, 조직 기반 다중 테넌시, 웹훅을 통한 데이터 동기화 등 전체 인증 수명 주기를 다룹니다. Core 3(2026) 마이그레이션을 중요시하며, 더 이상 사용되지 않는 <SignedIn>/<SignedOut>을 <Show> 컴포넌트로 대체하고 패키지를 통합합니다. 또한 미들웨어 우회 취약점을 해결하기 위해 '심층 방어' 접근 방식을 강조하여 미들웨어와 데이터 액세스 레이어 모두에서 라우트를 보호합니다. 이 스킬은 Claude Code, Cursor, Codex와 같은 AI 에이전트가 애플리케이션에 Clerk 인증을 올바르게 구현하기 위한 결정 프레임워크, 코드 예제 및 위험 플래그를 제공합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-auth-clerk

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Clerk 인증 패턴은(는) 무엇을 하나요?

빠른 안내: Clerk은 사전 구축된 UI 컴포넌트, 서버 측 헬퍼, 조직 기반 다중 테넌시를 제공하는 관리형 인증입니다. 라우트 보호에는 clerkMiddleware(), 조건부 렌더링에는 <Show>, 클라이언트 상태에는 훅, 서버 측 인증에는 auth()/currentUser()를 사용하세요. Clerk Core 3(2026)에서는 <SignedIn>/<SignedOut>을 <Show>로 대체하고, 미들웨어 파일 이름을 proxy.ts(Next.js 16+)로 변경하며, 패키지를 통합합니다.


<critical_requirements>

중요: 이 스킬 사용 전 필수 사항

모든 코드는 CLAUDE.md의 프로젝트 규칙을 따라야 합니다 (kebab-case, 명명된 내보내기, 임포트 순서, import type, 명명된 상수)

(모든 서버 측 임포트는 @clerk/nextjs/server를 사용해야 합니다 -- 절대 @clerk/nextjs에서 서버 헬퍼를 임포트하지 마세요)

(Clerk의 verifyWebhook 헬퍼를 사용하여 웹훅을 반드시 검증해야 합니다 -- 절대 확인되지 않은 웹훅 페이로드를 신뢰하지 마세요)

(더 이상 사용되지 않는 <SignedIn>/<SignedOut>/<Protect> 대신 <Show> 컴포넌트를 사용해야 합니다 -- Core 3에서 제거됨)

(전체 currentUser() 객체를 클라이언트에 전달하면 안 됩니다 -- 서버 측에 남아 있어야 하는 privateMetadata가 포함됩니다)

(미들웨어와 데이터 액세스 레이어 모두에서 라우트를 보호해야 합니다 -- 미들웨어만으로는 충분하지 않습니다)

</critical_requirements>


자동 감지: 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"

사용 시기:

  • 애플리케이션에 인증 및 사용자 관리 추가
  • 조직 기반 액세스 제어로 B2B 다중 테넌트 앱 구축
  • 사용자 정의 테마가 가능한 사전 구축된 로그인/회원가입 UI 컴포넌트 사용
  • 미들웨어 및 서버 측 권한 부여 검사로 라우트 보호
  • 웹훅을 통해 Clerk 사용자 데이터를 데이터베이스와 동기화

주요 패턴:

  • ClerkProvider 설정, 환경 변수, 미들웨어 구성
  • 사전 구축된 UI 컴포넌트 (<SignIn>, <SignUp>, <UserButton>, <Show>)
  • 클라이언트 측 훅 (useUser, useAuth, useSession, useOrganization)
  • 서버 측 인증 (auth(), currentUser()) -- 서버 컴포넌트, 라우트 핸들러, 서버 액션
  • clerkMiddleware() 및 createRouteMatcher()를 사용한 미들웨어 라우트 보호
  • 역할 및 권한 기반 조직 다중 테넌시
  • Svix 서명 검증을 통한 웹훅 처리

사용하지 말아야 할 때:

  • 자체 호스팅 인증 필요 (인증 데이터 저장소를 완전히 제어해야 함)
  • 타사 인증 서비스를 사용할 수 없음 (규정/규제 제약)
  • 간단한 API 키 인증으로 충분 (사용자 정의 미들웨어로 가능)
  • 예산 제약으로 관리형 서비스 사용 불가

상세 리소스:


철학

Clerk은 관리형 인증 플랫폼으로, 회원가입, 로그인, 세션 관리, 사용자 프로필, 조직, MFA 등 전체 인증 수명 주기를 처리합니다. 인증을 처음부터 구축하는 대신 Clerk의 SDK와 사전 구축된 컴포넌트를 통합하세요.

핵심 원칙:

  1. 심층 방어 -- 미들웨어 계층에서 라우트를 보호하고 모든 데이터 액세스 지점에서 인증을 확인하세요. 미들웨어만으로는 충분하지 않습니다 (CVE-2025-29927은 미들웨어 우회 취약점을 입증했습니다).
  2. 서버 우선 인증 -- 서버 컴포넌트와 라우트 핸들러에서 auth()와 currentUser()를 사용하세요. 반응형 클라이언트 측 상태가 필요할 때만 클라이언트 훅(useUser, useAuth)을 사용하세요.
  3. 사전 구축 선호 -- Clerk의 <SignIn>, <SignUp>, <UserButton> 컴포넌트를 사용하세요. 사전 구축된 컴포넌트가 요구 사항을 충족할 수 없는 경우에만 사용자 정의 흐름을 구축하세요.
  4. 다중 테넌시를 위한 조직 -- B2B 앱에는 역할 및 권한이 있는 Clerk 조직을 사용하세요. Clerk의 사용자 모델 위에 사용자 정의 테넌트 시스템을 구축하지 마세요.
  5. 웹훅 기반 동기화 -- 폴링이 아닌 웹훅을 통해 Clerk 데이터를 데이터베이스와 동기화하세요. 항상 verifyWebhook으로 웹훅 서명을 검증하세요.

Clerk 사용 시기:

  • 최소한의 사용자 정의 코드로 빠르게 인증이 필요할 때
  • 로그인/회원가입/사용자 관리를 위한 사전 구축 UI 컴포넌트를 원할 때
  • RBAC가 포함된 조직 기반 다중 테넌시가 필요할 때
  • 관리형 MFA, SSO(SAML/OIDC), 소셜 로그인을 원할 때

Clerk을 사용하지 말아야 할 때:

  • 인증 데이터 저장소를 완전히 제어해야 함 (자체 호스팅 필요)
  • 타사 인증 서비스를 사용할 수 없음 (규정/규제)
  • 앱에 간단한 API 키 인증만 필요
  • 예산 제약으로 관리형 서비스 사용 불가

핵심 패턴

패턴 1: ClerkProvider 및 미들웨어 설정

모든 Clerk 앱에는 앱을 감싸는 <ClerkProvider>와 라우트를 보호하는 clerkMiddleware()가 필요합니다. 전체 설정 예제는 examples/core.md를 참조하세요.

주요 규칙:

  • ClerkProvider는 <body> 안에 위치하며 <html>을 감싸지 않습니다 -- Core 3에서 필수
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY 환경 변수를 사용하고 키를 하드코딩하지 마세요
  • 미들웨어 파일은 Next.js 16+에서는 proxy.ts, Next.js <=15에서는 middleware.ts입니다
  • 웹훅 엔드포인트는 공용 라우트 목록에 있어야 하며 (verifyWebhook으로 별도 검증)
  • CLERK_WEBHOOK_SIGNING_SECRET은 웹훅 서명을 위한 환경 변수입니다 (CLERK_WEBHOOK_SECRET이 아님)
// 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();
  }
});

좋은 이유: 공용 라우트는 명시적으로 허용 목록에 포함되고, 나머지는 모두 인증이 필요하며, 웹훅 엔드포인트는 공용입니다 (별도로 검증됨)

// 나쁜 예: clerkMiddleware()에 콜백 없음
export default clerkMiddleware(); // 모든 라우트가 기본적으로 공용입니다!

나쁜 이유: 콜백 없는 clerkMiddleware()는 인증 데이터를 첨부하지만 어떤 라우트도 보호하지 않습니다


패턴 2: 사전 구축된 UI 컴포넌트와 <Show>

조건부 렌더링에는 <Show>를 사용하세요 (Core 3에서 <SignedIn>/<SignedOut>/<Protect>를 대체). 전체 예제는 examples/components.md를 참조하세요.

<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>

좋은 이유: <Show>는 Core 3 API이며, 문자열/객체/콜백 조건을 지원하고, 권한 없는 경우를 위한 fallback prop을 제공합니다

// 나쁜 예: 더 이상 사용되지 않음 -- Core 3에서 제거됨
<SignedIn>...</SignedIn>
<SignedOut>...</SignedOut>
<Protect role="admin">...</Protect>

나쁜 이유: <SignedIn>, <SignedOut>, <Protect>는 Core 3에서 제거되었으며, when prop과 함께 <Show>를 사용하세요

로그인/회원가입 페이지는 다단계 흐름(MFA, OAuth 콜백)을 위해 catch-all 라우트 세그먼트 [[...sign-in]]이 필요합니다.


패턴 3: 클라이언트 측 훅

반응형 인증 상태를 위해 클라이언트 컴포넌트에서 훅을 사용하세요. 데이터에 액세스하기 전에 항상 isLoaded를 확인하세요. 전체 예제는 examples/hooks.md를 참조하세요.

"use client";
const { isLoaded, isSignedIn, user } = useUser();
if (!isLoaded) return <div>로딩 중...</div>;
if (!isSignedIn) return <div>로그인해 주세요</div>;
// 이제 user.firstName, user.emailAddresses 등에 안전하게 액세스 가능

좋은 이유: 초기화 중에 undefined에 액세스하여 발생하는 hydration 오류를 방지하고, 로그아웃 상태에서 null에 액세스하여 발생하는 TypeError를 방지합니다

// 나쁜 예: guard 없이 user에 액세스
const { user } = useUser();
return <p>{user.firstName}</p>; // isLoaded=false 또는 isSignedIn=false일 때 런타임 오류

나쁜 이유: user는 로드될 때까지 undefined이고 로그아웃 시 null입니다

주요 훅:

  • useUser() -- 사용자 프로필 데이터 (이름, 이메일, 아바타)
  • useAuth() -- 세션 토큰(getToken()), userId, orgId, 권한 부여를 위한 has()
  • useOrganization() -- 활성 조직 데이터, 멤버십, 페이지네이션된 멤버 목록
  • useReverification() -- 민감한 작업 전 재인증 (Core 3)

Core 3에서 getToken()은 오프라인일 때 null 대신 ClerkOfflineError를 throw합니다 -- 항상 try/catch로 감싸세요.


패턴 4: 서버 측 인증

@clerk/nextjs/server에서 auth()와 currentUser()를 사용하세요. 전체 예제는 examples/server.md를 참조하세요.

auth() -- 가벼움, API 호출 없음, 세션 클레임 읽기:

import { auth } from "@clerk/nextjs/server";
const { userId, orgId, isAuthenticated, redirectToSignIn } = await auth();
if (!isAuthenticated) return redirectToSignIn();

currentUser() -- 전체 사용자 객체, 백엔드 API 호출:

import { currentUser } from "@clerk/nextjs/server";
const user = await currentUser();
// 중요: 클라이언트 컴포넌트에 전달하기 전에 안전한 필드만 선택
const safeData = { firstName: user.firstName, imageUrl: user.imageUrl };

중요한 이유: currentUser()는 결코 클라이언트에 도달해서는 안 되는 privateMetadata를 반환합니다. 필드를 명시적으로 선택하세요.

심층 방어: 미들웨어뿐만 아니라 모든 서버 액션과 라우트 핸들러에서 인증을 확인해야 합니다:

"use server";
const { userId } = await auth();
if (!userId) throw new Error("권한 없음");
// userId는 모든 쿼리/뮤테이션을 현재 사용자로 범위 제한

패턴 5: 역할 및 권한을 사용한 권한 부여

세분화된 액세스 제어에는 auth.protect() 또는 has()를 사용하세요. RBAC 패턴은 examples/organizations.md를 참조하세요.

// 미들웨어: 역할 기반 라우트 보호
await auth.protect((has) => has({ role: "org:admin" }));

// 서버 컴포넌트: 권한 확인
const { has } = await auth();
if (!has({ permission: "org:invoices:delete" })) redirect("/dashboard");

// 클라이언트: 역할별 조건부 렌더링
<Show when={{ role: "org:admin" }}><AdminPanel /></Show>

권한 형식: org:<feature>:<action> (예: org:invoices:create, org:reports:read)

기본 역할: org:admin (모든 권한), org:member (읽기 전용). 사용자 정의 역할은 Clerk 대시보드에서 구성합니다.


패턴 6: 웹훅 처리

Clerk 웹훅(Svix 기반)을 사용하여 사용자 데이터를 데이터베이스와 동기화하세요. 전체 핸들러는 examples/server.md를 참조하세요.

import { verifyWebhook } from "@clerk/nextjs/webhooks";

export async function POST(req: Request) {
  const evt = await verifyWebhook(req); // CLERK_WEBHOOK_SIGNING_SECRET 환경 변수 읽음
  // evt.type: "user.created" | "user.updated" | "user.deleted" | "organization.*" | ...
  // evt.data: UserJSON | OrganizationJSON | etc.
}

좋은 이유: verifyWebhook은 Svix 서명을 검증하고, 재시도를 방지하기 위해 항상 200을 반환하며, 웹훅 라우트는 미들웨어에서 공용입니다 (인증이 아닌 서명으로 검증됨)

// 나쁜 예: 서명 검증 없음
const body = await req.json(); // 누구나 가짜 데이터를 POST할 수 있습니다!
await db.insert(users).values(body.data); // 보안 취약점

나쁜 이유: 서명 검증이 없으면 누구나 가짜 웹훅 이벤트를 엔드포인트로 보낼 수 있습니다

@clerk/shared/types에서 타입 임포트 (Core 3): UserJSON, OrganizationJSON, OrganizationMembershipJSON


<decision_framework>

결정 프레임워크

클라이언트 vs 서버 인증

인증 데이터가 필요한 곳은 어디인가요?
|-- 서버 컴포넌트, 라우트 핸들러, 서버 액션
|   |-- userId/sessionId만 필요? --> auth()
|   |-- 전체 사용자 객체 필요? --> currentUser()
|   |-- 라우트 보호 필요? --> auth.protect()
|   +-- 조직 컨텍스트 필요? --> auth()가 orgId, orgRole 반환
|
+-- 클라이언트 컴포넌트 (대화형 UI)
    |-- 사용자 프로필 데이터 필요? --> useUser()
    |-- 세션/토큰 데이터 필요? --> useAuth()
    |-- 조직 데이터 필요? --> useOrganization()
    +-- 저수준 Clerk API 필요? --> useClerk()

라우트 보호 전략

어떤 종류의 라우트인가요?
|-- 공용 (랜딩, 로그인, 회원가입, 웹훅)
|   +-- isPublicRoute 매처에 추가, auth.protect() 건너뛰기
|
|-- 인증 필요 (대시보드, 프로필, 설정)
|   +-- 미들웨어의 auth.protect() + 데이터 레이어의 auth() 확인
|
+-- 권한 필요 (관리자, 조직별, 권한 게이트)
    |-- 역할 기반? --> auth.protect({ role: "org:admin" })
    +-- 권한 기반? --> auth.protect((has) => has({ permission: "org:feature:action" }))

컴포넌트 선택

어떤 인증 UI가 필요하신가요?
|-- 전체 로그인 페이지 --> catch-all 라우트의 <SignIn />
|-- 전체 회원가입 페이지 --> catch-all 라우트의 <SignUp />
|-- 로그인 버튼 (모달) --> <SignInButton />
|-- 사용자 아바타 + 메뉴 --> <UserButton />
|-- 전체 프로필 편집기 --> <UserProfile />
|-- 조직 전환기 --> <OrganizationSwitcher />
+-- 조건부 콘텐츠 --> <Show when="signed-in"> 또는 <Show when={{ role: "..." }}>

</decision_framework>


<red_flags>

위험 플래그

높은 우선순위 문제:

  • @clerk/nextjs 대신 @clerk/nextjs/server에서 서버 헬퍼를 임포트해야 함 (@clerk/nextjs는 서버 컴포넌트에서 작동하지 않음)
  • 더 이상 사용되지 않는 <SignedIn>/<SignedOut>/<Protect> 컴포넌트 사용 (Core 3에서 제거됨)
  • 전체 currentUser() 객체를 클라이언트 컴포넌트에 전달 (privateMetadata 노출)
  • verifyWebhook 서명 검증 없이 웹훅 페이로드 신뢰
  • 라우트 보호를 미들웨어에만 의존 (미들웨어는 우회될 수 있음)
  • @clerk/types 대신 @clerk/shared/types에서 타입 임포트 (Core 3 변경)
  • CSP(Content Security Policy) 없이 Clerk가 iframe을 렌더링하도록 허용 (클릭재킹 위험)
  • auth.protect() 실패 시 적절한 오류 처리 없음
  • 초기화 전에 isLoaded를 확인하지 않고 useUser() 데이터 액세스

의심스러운 패턴:

  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY를 소스 코드에 하드코딩
  • auth.protect()를 호출하지 않고 서버 컴포넌트에서 auth()만 사용
  • 웹훅 엔드포인트를 비공개로 표시하고 인증 토큰이 필요하다고 가정
  • 템플릿 리터럴을 사용하여 URL에 직접 userId 주입 (IDOR 취약점)
  • 미들웨어 없이 ClerkProvider만 사용
  • 요청당 여러 번 currentUser() 호출 (속도 저하)

</red_flags>


관련 리소스


프로젝트 요구 사항

의존성

{
  "@clerk/nextjs": "^6.0.0 || ^5.0.0",
  "next": "^14.0.0 || ^15.0.0 || ^16.0.0"
}

환경 변수

# 필수
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***
CLERK_SECRET_KEY=sk_test_***

# 웹훅에만 필요 (선택 사항이지만 권장)
CLERK_WEBHOOK_SIGNING_SECRET=whsec_***

파일 구조

├── app/
│   ├── layout.tsx          # ClerkProvider로 감싸기
│   ├── sign-in/
│   │   └── [[...sign-in]]/page.tsx  # <SignIn /> 렌더링
│   ├── sign-up/
│   │   └── [[...sign-up]]/page.tsx  # <SignUp /> 렌더링
│   └── api/
│       └── webhooks/
│           └── route.ts    # verifyWebhook() 사용
├── middleware.ts           # 또는 proxy.ts (Next.js 16+)
└── .env.local

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

관련 스킬