Todo app - auth
Oauth with Next.js Auth.js
October 21, 2024
Next.js에서 로그인 시스템을 개발할 때, 가장 효율적인 방법 중 하나는 Google과 같은 서드파티 플랫폼을 이용하여 계정을 연동하는 것입니다. 저는 Next.js의 공식 인증 라이브러리인 Auth.js(이전 명칭: next-auth)를 사용하여 이를 구현하기로 결정했습니다.
로그인 구현 시 주요 고려사항
로그인 기능을 구현할 때는 다음 세 가지 주요 측면을 고려해야 합니다:
- 로그인 프로세스
- 로그인 후 세션 획득
- 보호된 라우트에 대한 세션 확인
각 요구사항의 구현 방법을 살펴보겠습니다.
1. 로그인 설정
로그인 설정을 위해 auth.config.ts와 auth.ts 두 파일을 생성해야 합니다.
// auth.config.ts
const config: DynamoDBClientConfig = {
credentials: {
accessKeyId: process.env.AUTH_DYNAMODB_ID as string,
secretAccessKey: process.env.AUTH_DYNAMODB_SECRET as string,
},
region: process.env.AUTH_DYNAMODB_REGION,
endpoint: process.env.AUTH_DYNAMODB_END_POINT,
};
const client = DynamoDBDocument.from(new DynamoDB(config), {
marshallOptions: {
convertEmptyValues: true,
removeUndefinedValues: true,
convertClassInstanceToMap: true,
},
});
export const authConfig = {
providers: [
Google({
clientId: process.env.NEXT_PUBLIC_AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
}),
],
session: {
strategy: 'database',
},
callbacks: {
async redirect({ url, baseUrl }) {
return url.startsWith(baseUrl) ? url : baseUrl;
},
},
adapter: DynamoDBAdapter(client),
debug: false,
} satisfies NextAuthConfig;
// auth.ts
export const { auth, handlers, signIn, signOut } = NextAuth({
...authConfig,
});
또한 인증을 위한 라우트 핸들러를 생성해야 합니다:
// api/auth/[...nextauth]/route.ts
import { handlers } from '@/app/auth';
export const { GET, POST } = handlers;
이 설정을 통해 Auth.js에서 제공하는 로그인, 로그아웃 및 세션 정보 조회 메서드를 사용할 수 있습니다. auth.config 에 보면 DynamoDBAdapter(client) 부분이 있는데 이것은 유저, 세션 정보를 데이터베이스로 저장 할 때에 어떤 디비를 사용할지 설정하는 부분입니다. auth.js에서는 제가 이 프로젝트의 디비로 사용하고자 하는 DynamoDB에 대한 어탭터로 제공하므로 설정만 하면 자동으로 디비와 연동이 됩니다.
2. 세션 획득
세션 정보를 획득하기 위해 애플리케이션의 최상위 레벨에 ClientSessionProvider를 선언해야 합니다.
// layout.ts
<ClientSessionProvider>{children}</ClientSessionProvider>
이를 통해 하위 컴포넌트에서 세션 정보에 접근할 수 있습니다.
3. 보호된 라우트
보호된 라우트를 처리하기 위해 세션 정보를 확인하고 필요시 리다이렉트하는 미들웨어를 구현할 수 있습니다.
// middleware.ts
function isProtectedRoute(pathname: string): boolean {
return config.matcher.some((route) => pathname.startsWith(route));
}
export default auth((req) => {
if (isProtectedRoute(req.nextUrl.pathname)) {
if (!req.auth) {
return NextResponse.redirect(${process.env.NEXT_PUBLIC_APP_URL}/login);
}
return NextResponse.next();
}
});
export const config = { matcher: ['/main', '/settings'] };
이 미들웨어는 지정된 패턴과 일치하는 라우트에 대해 세션 정보를 확인하고, 세션이 없는 경우 로그인 페이지로 리다이렉트합니다.
UX 고려사항
이러한 기능을 구현하는 과정에서 세션 정보 획득 타이밍과 관련된 UX 문제를 발견했습니다.
서버 컴포넌트는 빠르게 렌더링되지만, ClientSessionProvider는 클라이언트 측에서 세션 정보를 주입하여 타이밍 불일치가 발생합니다.
이 문제는 인증에만 국한된 것이 아니라 Next.js에서 UI 기능을 구현할 때 흔히 발생합니다. 이를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다
서버 컴포넌트에서 데이터를 생성하고 props나 context를 통해 클라이언트 컴포넌트에 주입합니다.
또는 서버 컴포넌트에서 React Query를 사용하여 프리페칭을 수행하고, 클라이언트 컴포넌트에서 즉시 프리페치된 데이터를 사용합니다.
이러한 접근 방식을 통해 더 빠른 UI 렌더링과 더 나은 사용자 경험을 제공할 수 있습니다.
이러한 솔루션을 구현함으로써 Next.js에서 로그인, 세션 관리, 보호된 라우트 처리를 효과적으로 다루는 강력한 인증 시스템을 만들 수 있으며, 동시에 우수한 사용자 경험을 유지할 수 있습니다.