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 키 인증으로 충분 (사용자 정의 미들웨어로 가능)
- 예산 제약으로 관리형 서비스 사용 불가
상세 리소스:
- reference.md - 결정 프레임워크, 훅 빠른 참조, Core 3 마이그레이션 치트 시트
- examples/core.md - ClerkProvider, 환경 변수, 미들웨어 구성
- examples/components.md - 사전 구축 컴포넌트, 사용자 정의, appearance 속성
- examples/hooks.md - useUser, useAuth, useSession, 로딩 상태, 조건부 렌더링
- examples/server.md - 서버 컴포넌트, API 라우트, 서버 액션, 웹훅 처리
- examples/organizations.md - 조직 관리, 역할, 권한, RBAC
철학
Clerk은 관리형 인증 플랫폼으로, 회원가입, 로그인, 세션 관리, 사용자 프로필, 조직, MFA 등 전체 인증 수명 주기를 처리합니다. 인증을 처음부터 구축하는 대신 Clerk의 SDK와 사전 구축된 컴포넌트를 통합하세요.
핵심 원칙:
- 심층 방어 -- 미들웨어 계층에서 라우트를 보호하고 모든 데이터 액세스 지점에서 인증을 확인하세요. 미들웨어만으로는 충분하지 않습니다 (CVE-2025-29927은 미들웨어 우회 취약점을 입증했습니다).
- 서버 우선 인증 -- 서버 컴포넌트와 라우트 핸들러에서
auth()와currentUser()를 사용하세요. 반응형 클라이언트 측 상태가 필요할 때만 클라이언트 훅(useUser,useAuth)을 사용하세요. - 사전 구축 선호 -- Clerk의
<SignIn>,<SignUp>,<UserButton>컴포넌트를 사용하세요. 사전 구축된 컴포넌트가 요구 사항을 충족할 수 없는 경우에만 사용자 정의 흐름을 구축하세요. - 다중 테넌시를 위한 조직 -- B2B 앱에는 역할 및 권한이 있는 Clerk 조직을 사용하세요. Clerk의 사용자 모델 위에 사용자 정의 테넌트 시스템을 구축하지 마세요.
- 웹훅 기반 동기화 -- 폴링이 아닌 웹훅을 통해 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>
관련 리소스
- reference.md - 빠른 참조, 결정 트리, Core 3 마이그레이션 가이드
- examples/core.md - ClerkProvider, 환경 변수, 미들웨어 구성
- examples/components.md - 사전 구축 컴포넌트, 테마, 사용자 정의
- examples/hooks.md - 클라이언트 훅, 로딩 상태, 조건부 렌더링
- examples/server.md - 서버 컴포넌트, API 라우트, 서버 액션, 웹훅
- examples/organizations.md - 조직, 역할, 권한, RBAC
프로젝트 요구 사항
의존성
{
"@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