NextAuth.js로 인증을 구현할 때 세션 전략 선택은 성능에 직접적인 영향을 미친다. pixelDiff 프로젝트에서 Figma OAuth를 구현하면서 DB 세션에서 JWT로 전환한 경험을 정리한다.
문제 상황
Figma OAuth 연동 후 API 응답이 체감될 정도로 느렸다. 원인을 추적해보니 매 요청마다 세션 테이블을 조회하는 데서 병목이 발생하고 있었다.
[요청] → [세션 테이블 조회] → [사용자 정보 반환] → [응답]
↑
약 400ms 소요
세션 조회 자체는 단순한 쿼리지만, 네트워크 왕복(Supabase PostgreSQL)과 커넥션 오버헤드가 누적되면서 체감 지연이 발생했다.
선택지 분석
| 전략 | 동작 방식 | 장점 | 단점 |
|---|---|---|---|
| DB 세션 | 매 요청마다 세션 테이블 조회 | 서버에서 세션 무효화 가능, 세션 데이터 용량 제한 없음 | 매 요청마다 DB 조회 필요 |
| JWT | 토큰 서명 검증만 수행 | DB 조회 없음, 빠름 | 토큰 크기 제한, 서버 측 즉시 무효화 어려움 |
선택 근거
pixelDiff의 상황:
- 세션에 저장할 데이터가 적다 - userId, role 정도만 필요
- 즉시 세션 무효화가 필요 없다 - 강제 로그아웃 기능 불필요
- API 호출이 잦다 - 캔버스 조작 시 초당 수십 회 요청 발생
JWT가 적합했다. 토큰 크기 제한(~4KB)도 우리 케이스에선 문제 없었다.
구현
1. 세션 전략 변경
// apps/web/lib/auth.ts
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
session: {
strategy: 'jwt', // DB 세션 대신 JWT 사용
maxAge: 30 * 24 * 60 * 60, // 30일
},
// ...
};
PrismaAdapter는 그대로 유지한다. JWT 전략에서도 어댑터는 사용자/계정 정보 저장에 사용된다.
2. JWT 콜백 구현
JWT 전략의 핵심은 jwt 콜백이다. 두 가지 상황을 구분해야 한다:
callbacks: {
async jwt({ token, account, user }) {
// ─────────────────────────────────
// 초기 로그인: account와 user가 존재
// ─────────────────────────────────
if (account && user) {
const dbUser = await prisma.user.findUnique({
where: { id: user.id },
select: { role: true },
});
return {
...token,
accessToken: account.access_token,
refreshToken: account.refresh_token,
expiresAt: account.expires_at,
userId: user.id,
role: dbUser?.role ?? 'user',
};
}
// ─────────────────────────────────
// 후속 요청: 토큰 그대로 반환
// ─────────────────────────────────
return token;
},
async session({ session, token }) {
if (session.user && token) {
session.user.id = token.userId as string;
session.user.role = token.role as string;
}
return session;
},
},
핵심 포인트: 초기 로그인 시에만 DB를 조회하고, 이후 요청에서는 토큰에 저장된 정보를 그대로 사용한다. 이게 400ms → 5ms의 비결이다.
3. Figma OAuth 특이사항: HTTP Basic Auth
Figma OAuth는 토큰 교환 시 표준 방식(client_id/secret을 body에 포함)이 아닌 HTTP Basic Auth를 요구한다.
token: {
url: 'https://api.figma.com/v1/oauth/token',
async request(context) {
// Figma는 HTTP Basic Auth 필수
const credentials = Buffer.from(
`${FIGMA_CLIENT_ID}:${FIGMA_CLIENT_SECRET}`
).toString('base64');
const response = await fetch('https://api.figma.com/v1/oauth/token', {
method: 'POST',
headers: {
Authorization: `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
redirect_uri: context.provider.callbackUrl,
code: context.params.code,
grant_type: 'authorization_code',
}).toString(),
});
return { tokens: await response.json() };
},
},
NextAuth의 기본 토큰 교환 로직을 사용하면 인증 실패한다. token.request를 직접 구현해야 한다.
4. 재로그인 시 토큰 갱신
JWT 전략에서 한 가지 주의점: Figma 토큰이 만료되면 재로그인이 필요한데, 이때 DB의 토큰도 업데이트해야 한다.
events: {
async signIn({ user, account }) {
if (account?.provider === 'figma' && account.access_token) {
await prisma.account.updateMany({
where: {
userId: user.id,
provider: 'figma',
},
data: {
access_token: account.access_token,
refresh_token: account.refresh_token,
expires_at: account.expires_at,
},
});
}
},
},
Figma API 호출 시 DB에 저장된 토큰을 사용하므로, 재로그인할 때마다 최신 토큰으로 갱신해준다.
결과
| 지표 | Before (DB 세션) | After (JWT) |
|---|---|---|
| 세션 조회 | ~400ms | ~5ms |
| DB 부하 | 매 요청마다 조회 | 로그인 시에만 조회 |
JWT 전략이 항상 정답은 아니다. 다음 조건에 해당하면 JWT를 고려해볼 만하다:
- 세션에 저장할 데이터가 적다 (수 KB 이하)
- 서버 측 즉시 세션 무효화가 필요 없다
- API 호출 빈도가 높다
반대로 관리자가 특정 사용자를 즉시 로그아웃시켜야 하거나, 세션에 대용량 데이터를 저장해야 한다면 DB 세션이 적합하다.
결국 "어떤 전략이 좋은가"보다 "우리 상황에 어떤 전략이 맞는가"를 먼저 따져봐야 한다.
프런트엔드 엔지니어, QA 엔지니어 그리고 디자이너를 위한
" ALL IN ONE " QA 서비스
https://pixeldiff.turtle-tail.com
'FrontEnd > React' 카테고리의 다른 글
| Next.js로만 백엔드, 프론트엔드 구축하기 (0) | 2026.01.24 |
|---|---|
| Zustand로 Undo/Redo 구현하기 (0) | 2026.01.13 |
| Next.js 14 완벽 번역 (1) | 2023.10.27 |
| React Todo에서 UX개선 (1) | 2023.04.28 |
| 리액트에서 ...state 와 prev => ...prev 의 차이 (0) | 2023.04.25 |
