이번 포스팅에서는 풀스택 애플리케이션을 위한 소셜 로그인을 구현하는 과정을 담았다.
토이 프로젝트를 처음 시작하는 사람이라면 막막하게 느껴질 부분도 많을 것이고, 내가 겪었던 부트캠프의 많은 멘토께서는 회원 기능을 후순위로 구현하라는 조언을 많이 해주셨다. 구현 자체에도 시간이 걸리지만 구현 이후 테스트하면서 개발할 때 일일이 로그인하는 과정이 번거로워 개발 시간을 잡아 먹기 때문이다. 따라서 회원 가입 및 로그인 기능은 프로덕트의 기본 기능이지만서도 앞의 이유와 함께 인증, 인가, 토큰 관리 등을 신경쓰는 데 있어 구현 난이도가 높다고 생각한다.
풀스택 프레임워크 중 하나인 NextJS에서는 이러한 부담을 줄이기 위한 인증 솔루션 라이브러리를 제공하는데, 그것이 바로 Next-auth이다.
Next-auth는 위에 서술한대로 기존 NextJS와 호환되는 라이브러리로 시작했지만, 현재는 그 외의 프레임워크와도 연동이 될 수 있도록 확장하며, v5부터는 이름도 Auth.js
로 변경했다.
(참고: Introduction - Auth.js)
그래도 아직은 Next-auth가 익숙하니 앞으로도 Next-auth(v5)로 명명하겠다.
Next-auth는 아래와 같은 장점으로 주로 쓰이며, 이번 포스팅에서도 관련 내용이 등장할 예정이다.
- Google, Facebook, Auth0, Apple, Kakao 등 82개 이상의 소셜 로그인을 지원
- MySQL, Postgres, Prisma, Drizzle 등의 23개 이상의 데이터베이스, ORM을 지원
- NextJS, SolidStart, SvelteKit, Express와 같은 현대적인 프레임워크와의 연동
- 암호화된 JWT 세션
하지만 작성일 기준(24.04.28) Next-auth(v5)의 경우 공식 문서가 작성되는 중이며, 공식 문서에 있는 코드 또한 타입 에러를 발생시키는 경우가 많고 예시가 부족하여 불친절하다는 느낌을 많이 받았다. 따라서 개발하는 데 있어 라이브러리 코드를 직접 열어봐야 하는 불상사가 발생하곤 한다. 아직 베타 버전이란 점에서 이러한 불편함이 종종 있다.
- NextJS app router 우선 지원
- 간소화된 설정(공유 구성config, 추론된 환경 변수)
getServerSession
,getSession
,withAuth
,getToken
,useSession
이auth()
로 통합- 지원되는 NextJS 버전 >= 13.4
그 외 더 자세한 사항은 공식 문서(Upgrade Guide (v5) - Auth.js.dev)를 참고하길 바란다.
Next-auth는 회원의 인증, 인가에 집중한 라이브러리이고, 후술할 Prisma와 Supabase는 인증에 성공한 회원의 정보를 저장할 데이터베이스와 관련된 라이브러리이다.
Prisma는 NodeJS, TypeScript 호환 데이터베이스 ORM이다. 정적 타입 시스템을 지원하기 때문에 데이터베이스 쿼리 작업 시 자동 완성을 통해 오류를 미리 방지할 수 있다. 데이터베이스 스키마를 코드 기반으로도 관리할 수 있어 개발과 유지 보수를 편리하게 할 수 있다.
또한 NextJS, GraphQL, NestJS, Express, Apollo, hapi와 같은 프레임워크와 연동되어 JavaScript 기반 풀스택을 고려하고 있다면 Prisma가 적절한 선택이 될 수 있다.
Supabase는 Firebase의 대안이 될 수 있는 PostgresSQL 호스팅 오픈 소스 서비스이다. Supabase의 목표는 결국 PostgresSQL를 쉽게 사용할 수 있도록 하는 데에 있다. Firebase가 제공하는 대부분의 기능을 개발하고 있지만 1대1 대응으로의 대안은 아니라고 말한다. Firebase와는 달리 Prisma ORM에 직접적으로 access할 수 있다.
더 자세한 아키텍쳐와 원칙은 공식 문서(Architecture - supabase DOCS)를 참고하길 바란다.
왜 굳이 Supabase를 prisma와 쓰는가에 대한 의문은 Reddit의 Why would you use Prisma with Supabase?라는 질문의 답변에서 확인할 수 있는데, 요약하자면 다음과 같다.
개발자들이 Prisma와 Supabase를 함께 사용하는 주된 이유는 PostgreSQL과 PgBouncer(PostgresSQL을 위한 경량 connection poller)를 무료로 제공하는 유일한 호스팅 제공자 중 하나이기 때문입니다. 특히 NextJS와 Vercel과 같은 서버리스 플랫폼에 백엔드 로직을 배포하는 경우에 유용합니다. 또한 Supabase가 connection polling이 필요하지 않도록 데이터 API를 제공합니다. 마지막으로 실시간 애플리케이션을 구축하는 데 유용한 데이터베이스 구독 기능이 있습니다.
혹시나 실시간 애플리케이션을 고려하고 있다면 확실하게 공감할 수 있는 선택의 이유가 될 것이다. 그런데 토이 프로젝트 중 소규모를 생각하고 있다면 관리형 호스팅 서비스를 사용하는 데 있어 사실 '무료'가 가장 중요한 선택 이유가 되지 않을까 싶다.
위에 서술한 기술 스택을 바탕으로 구글 로그인 후 회원 정보를 Supabase에 삽입하는 것까지 구현을 해보도록 하겠다. 자세한 코드는 github repository에서 단계별로 커밋된 히스토리를 참고하길 바란다.
npx create-next-app@latest <project-name>
✔ Would you like to use TypeScript? … No / <Yes>
✔ Would you like to use ESLint? … No / <Yes> // 자율적으로 선택 가능
✔ Would you like to use Tailwind CSS? … No / <Yes> // 자율적으로 선택 가능
✔ Would you like to use `src/` directory? … No / <Yes> // 자율적으로 선택 가능
✔ Would you like to use App Router? (recommended) … No / <Yes>
✔ Would you like to customize the default import alias (@/*)? … No / <Yes> // 자율적으로 선택 가능
✔ What import alias would you like configured? … @/*
npm run dev
프로젝트가 잘 생성된 것을 알 수 있다.
기본 구성은 필요없으니 지우고 로그인, 로그아웃에 필요한 Header만 추가하겠다.
npm install prisma --save-dev
npm install @prisma/client @auth/prisma-adapter
npm install supabase --save-dev
Supabase에도 토큰을 세션이나 쿠키에 저장하는 auth 기능을 구현할 수 있는 helper 패키지가 있다. 하지만 Supabase만의 의존성이 존재하기에 현재 포스팅에서는 해당 기능은 Next-auth(v5)로 구현한다.
npx prisma init
위 command에 따라 Prisma 초기화를 진행하면 schema.prisma
가 함께 생성된다. 해당 파일에 데이터베이스 스키마를 작성하면 된다. 프로덕트에 따라 달라지는 스키마이지만, 그 전에 Next-auth와 Supabase를 쓰는 경우 필요한 설정이 있다.
.env
, URL 설정Next-auth와 Prisma를 함께 사용할 때 기본 스키마 템플릿이 존재한다. 필수로 설정되어야 하는 것은 아니기에 자율적으로 구성해도 되지만 일반적으로 어느 상황에서든 적용되는 구조가 있다. 특히나 required key 값은 꼭 맞춰줘야 한다.
참고: Database Adapters - Auth.js.dev
위의 참고 문서에서는 Next-auth(v5)와 데이터베이스/백엔드 시스템을 연결하는 데 있어 사용자 정보를 저장하려면 Database Adapter
가 필요하다고 말한다.
만약 사용자 정보를 직접 구축한 데이터베이스로 관리하지 않는 경우, 세션 관리에 대한 요구 사항이 없는 경우, 간단한 인증 요구 사항인 경우에서는 사용하지 않아도 된다.
schema.prisma
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
구글 로그인을 구현하기 위해서는 구글에서 제공하는 API를 사용해야 한다.
구글 클라우드 콘솔에서 새로운 프로젝트를 생성한다.
@prisma/client
설정이번 설정에 대한 자세한 사항은 Next-auth 공식 문서를 참고하면 된다.
Next-auth
설치npm install next-auth@beta
npx auth secret
src/auth.ts
import NextAuth from "next-auth"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [],
})
src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers
src/middleware.ts
export { auth as middleware } from "@/auth"
src/auth.ts
import NextAuth, { NextAuthConfig } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
export const authOptions: NextAuthConfig = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
secret: process.env.AUTH_SECRET,
};
export const { handlers, signIn, signOut, auth } = NextAuth({
...authOptions,
});
결과물도 함께 보여주세요