Written by
Parkdev
on
on
[Next.js] Auth 관리 라이브러리 Clerk
회원가입을 쉽게 만들어주는 라이브러리 Clerk
- 이 라이브러리를 사용하면 쉽게 회원가입 페이지를 생성할 수 있다.
- 설치
npm install @clerk/nextjs
- env key발급 및 설정
- clerk.com을 회원가입하고 프로젝트용 key를 발급받자
- root경로에 `.env’파일 생성
- gitignore에 .env 추가 잊지 말자 - env key 정보 업데이트
- 프로젝트에 Import
- root/layout.tsx에 import할 수도 있지만, MSA를 위해서 별도의 파일에 설정해보자
//{root}/(platform)/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
const PlatformLayout = ({
children
}: {
children: React.ReactNode;
}) => {
return (
<ClerkProvider>
{children}
</ClerkProvider>
)
})
비고. Clerk은 현지화 옵션도 제공한다.
import { ClerkProvider } from "@clerk/nextjs";
//npm install @clerk/localizations 이후
import { koKR } from "@clerk/localizations";
const PlatformLayout = ({ children }: { children: React.ReactNode }) => {
return <ClerkProvider localization={koKR}>{children}</ClerkProvider>;
};
export default PlatformLayout;
- middleware 설정
// 사용자 인증 및 관리를 위한 미들웨어 설정
import { authMiddleware } from "@clerk/nextjs";
// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware
export default authMiddleware({});
export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};
비고. 정규표현식의 해석
.
: 어떤 문자든지 일치시킵니다(줄바꿈 문자 제외)..+
: 하나 이상의 어떤 문자를 일치시킵니다.\\.
: 실제 점(.
) 문자를 일치시킵니다.[\\w]+
: 하나 이상의 단어 문자(알파벳, 숫자, 밑줄)를 일치시킵니다.$
: 문자열의 끝을 나타냅니다.|
: OR 연산자로, 이전 패턴 또는 다음 패턴을 일치시킵니다._next
: 실제_next
문자열을 일치시킵니다.(?!...)
: 부정 전방 탐색으로, 괄호 안의 패턴을 만족하지 않는 위치를 찾습니다..*
: 어떤 문자든지 0번 이상 반복되는 패턴을 일치시킵니다.=> (
{하나이상의문자}.{하나이상의단어문자} [즉, 파일 확장자] 또는
_next`로 시작하는 문자를 제외한 단어, “/”, “api or trpc 로 시작하는 모든 라우트에 미들웨어를 적용”
- 버튼 생성
-
Custom sign-up sign-in page setup
https://clerk.com/docs/references/nextjs/custom-signup-signin-pages
// app/(platform)/(clerk)/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";
export default function Page() {
return <SignIn />;
}
// sign-up도 동일하게 설정하면된다.
비고2. [[…sign-in ]] 폴더 명에 대해서
[…] 을포함한 폴더명은 모든 하위 경로를 포함하는 라우트를 지정하는 폴더 명이다.
- …는
catch all
라우트 를 뜻하는 의미로, 모든 하위경로를 포함한다는 의미이다.- [[…]] 은 Optional Catch-all Segement로 […] 와 동일한 기능을 가지지만 하위경로가 없어도 된다 (하위 경로가 없다면
router.query.id
값은 undefined로 정의된다.
getServerSideProps
,getStaticProps
,getInitialProps
또는 페이지 컴포넌트 자체에서router.query
를 통해 하위경로 값을 가져올 수 있다.
- 예를 들어,
/app/org/1/2/3
경로로 접근하면,router.query.organization
은['1', '2', '3']
이 출력된다.
- .env 업데이트
...env keys
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
- 접속 시도
- 이때 접속 시도 시 홈페이지에 접속해도 sign in page로 redirect 된다.
- 기본적으로 모든 페이지는 보안 설정이 되어있기 때문에 어떤 페이지를 가더라도 로그인을 하지 않으면 로그인 페이지로 연결된다.
- 비회원이 페이지를 볼수 있게 만들기 위해서는 middleware의 authMiddleware에 조건을 추가해야한다.
//middleware.ts
export default authMiddleware({
publicRoutes: ["/"],
});
auth, CurrentUser사용해보기
// app/(Platform)/protected
import { auth, currentUser } from "@clerk/nextjs";
const ProtectedPage = async () => {
const user = await currentUser(); // 사용자 정보를 반환
const { userId } = auth(); //사용자의 ID키를 반환
return (
<div>
User: {user?.firstName}
userId: {userId}
</div>
);
};
export default ProtectedPage;
// ver.2
"use client";
import { useAuth, useUser } from "@clerk/nextjs";
const ProtectedPage = () => {
const { userId } = useAuth();
const { user } = useUser();
return (
<div>
User: {user?.firstName}
userId: {userId}
</div>
);
};
export default ProtectedPage;
User 버튼 만들기 예제
"use client";
import { UserButton } from "@clerk/nextjs";
const ProtectedPage = () => {
return (
<div>
<UserButton afterSignOutUrl="/" />
</div>
);
};
export default ProtectedPage;
Organization 만들기
- Clerk dashboard > Organiztions Setting에서 `enable organiztions를 켜준 후
//app/(platform)/(clerk)/select-org/[[...select-org]]
import { OrganizationList } from "@clerk/nextjs";
export default function CreateOrganizationPage() {
return (
<OrganizationList
hidePersonal // 개인 설정 버튼 제거
afterSelectOrganizationUrl="/organization/:id" // 선택후 이동
afterCreateOrganizationUrl="/organization/:id" // 생성후 이동
/>
);
}
-
위처럼 파일을 만들면 그룹 생성 페이지가 노출되며 그룹을 새로 생성 할 수 있다.
-
이후 /organization/:id에 연결할 공용 대시보드 화면을 작성하면 된다.
// (platform)/(dashboard)/organization/[organizationId]
import { OrganizationSwitcher, auth } from "@clerk/nextjs";
const OrganizationIdPage = () => {
//const { userId, orgId } = auth();을통해 orgId도 확인 가능하다.
return (
<div>
// 대시보드 내용
<OrganizationSwitcher hidePersonal />
</div>
);
};
export default OrganizationIdPage;
메인페이지에 접속했을때 Orgpage로 Redirect 해줘야하는 경우 (Clerk 사용시)
- 기본적으로는 도메인에 접속하면 소개하는 페이지로 접속된다.
- 하지만 권한에 따라 보여줘야할 페이지가 달라질경우 middleware에서 제한을 걸어줘야한다.
//middleware.ts
import { authMiddleware } from "@clerk/nextjs";
export default authMiddleware({
publicRoutes: ["/"],
afterAuth(auth, req) { //clerk에서 기본으로 제공해주는 함수
// 예외 1. 로그인한상태로 Public root에 접속 했을때
if (auth,userId && auth.isPublicRoute) {
let path = "/select-org";
if (auth.orgId) { // 그리고 사용자가 orgID가 존재하면 (회사 그룹에 속해있으면)
path= `/organization/${auth.orgId}` //경로 수정
}
const orgSelection = new URL(path, req.url //새로운 path로 url을 변경해서
return NextResponse.redirect(orgSelection); // redirect시킨다.
}
// 예외2. 로그인 하지 않은 상태로 다른 경로에 접속했을때 (이를 설정하지않으면 404error 페이지로 이동된다.)
if (!auth.userId && !auth.isPublicRoute) {
return redirectToSignIn({ returnBackUrl: req.url });
}
// 예외3. 로그인 했지만 그룹에 속해있지 않으면서,
if (auth.userId && !auth.orgId && req.nextUrl.pathname !== "/select-org") {
const orgSelection = new URL("/select-org", req.url);
return NextResponse.redirect(orgSelection);
}
}
});
URL을 통한 회사 변경 기능 구현
- 기본적으로 url을 통해 회사 변경은 불가능하도록 설정되어있다
- 이를 해결하려면 다음과 같이 설정하면된다. (현 프로젝트처럼 여러개의 그룹nav를 한 페이지에 구현하려면 필수로 설정해야한다.)
// org상세 페이지를 정의한 app/(platform)/(dashboard)/organization/[organizationId]/layout.tsx
import { OrgControl } from "./_components/org-control";
const OrganizationIdLayout = ({ children }: { children: React.ReactNode }) => {
return (
<>
<OrgControl /> // 회사변경 로직을 정의한 useEffect를 정의한 컴포넌트 파일
{children}
</>
);
};
export default OrganizationIdLayout;
// _components/org-control.tsx
// CSR을 위한 컴포넌트 정의 지시어
"use client";
import { useEffect } from "react";
import { useParams } from "next/navigation";
import { useOrganizationList } from "@clerk/nextjs";
export const OrgControl = () => {
const params = useParams(); //파라미터의 회사Id 추출
const { setActive } = useOrganizationList(); // clerk의 활성화 정의 함수
useEffect(() => {
if (!setActive) return; //정의된 회사가 없으면 pass
setActive({
organization: params.organizationId as string, //활성 회사를 현 Url회사로 변경
});
}, [setActive, params.organizationId]);
};
비고.
"use client"
파일에 정의하면 하위 구성 요소를 포함하여 모든요소를 클라이언트 컴포넌트로 간주한다.