NextAuth v4 + Credentials Provider로 Spring 백엔드의 Bearer 토큰을 받아 session.user.token으로 클라이언트에 노출 중. httpOnly 쿠키에 저장하지만 JS에서 꺼내 쓰므로 XSS 방어가 부분적.
| 선택지 | 인증 라이브러리 | 아키텍처 | 전환 비용 | 비고 |
|---|---|---|---|---|
| A | NextAuth v4 유지 | 현행 (Semi-BFF) | 없음 | 변경 없음, 알려진 취약점 유지 |
| B | Auth.js v5 전환 | 현행 또는 BFF | 중간 | 공식 업그레이드 경로, breaking changes 있음 |
| C | Better Auth | BFF (자체 세션) | 높음 | 자체 DB 필요, Org/Role 빌트인 |
| D | NextAuth v4 유지 | + BFF 프록시 추가 | 중간 | 라이브러리 변경 없이 보안 강화 |
이유: 1~2주 전환 목표에 부합하고, 라이브러리 교체 없이 보안(토큰 JS 노출 제거)을 확보. Auth.js v5는 아직 stable이 아닌 API 변경이 잦고, Better Auth는 DB 스키마 협의가 선행되어야 함. Organization/Role은 백엔드가 이미 관리할 예정이므로 인증 라이브러리에서 담당할 필요 없음.
단, Better Auth의 Org/Role 빌트인이 매력적이라면 C를 중기 로드맵에 배치하는 것도 유효. 이 경우 백엔드팀과 DB 테이블 협의가 선행 조건.
session.user.token으로 토큰 JS 노출 → httpOnly 이점 반감auth-token 쿠키 fallback 미정리session.user.token으로 꺼내서 JS에 노출하는 구조. 이는 httpOnly의 보안 이점을 상당 부분 무력화함. B2B 내부 툴이라 실질 위험은 낮지만, 구조적으로 개선 여지가 있음.
| 기준 | NextAuth v4 (현재) | Auth.js v5 | Better Auth |
|---|---|---|---|
| 성숙도 | 안정 3년+ | 베타→RC | v1.2+ 빠르게 성장 |
| GitHub Stars | ~25k (Auth.js 통합) | 동일 레포 | ~20k+ (2024말 급성장) |
| npm 주간 다운로드 | ~1.5M | ~500K | ~80K (급증 추세) |
| Credentials Provider | ✅ 지원 | ✅ 지원 | ✅ 이메일+비밀번호 빌트인 |
| 세션 전략 | JWT 또는 DB | JWT 또는 DB | DB 세션 기본 (자체 테이블) |
| 자체 DB 필요 | ❌ JWT면 불필요 | ❌ JWT면 불필요 | ✅ 필수 |
| Organization 빌트인 | ❌ | ❌ | ✅ 플러그인 |
| RBAC 빌트인 | ❌ (직접 구현) | ❌ (직접 구현) | ✅ 플러그인 |
| 2FA | 직접 구현 | 직접 구현 | ✅ 플러그인 |
| Next.js 16 호환 | ✅ | ✅ (App Router 최적화) | ✅ |
| 마이그레이션 비용 | - | 중간 (API 변경 다수) | 높음 (전면 교체) |
| 외부 백엔드 연동 | ✅ Credentials로 가능 | ✅ 동일 | 간접적 (자체 인증 우선) |
next-auth → @auth/nextjs 패키지명 변경 (v5 후기)getServerSession(authOptions) → auth() 함수로 통합unstable_getServerSession 제거auth.ts에서 handlers, auth, signIn, signOut exportauth() 직접 사용 (Edge Runtime 호환)auth() 하나로 서버 어디서든 세션 확인2024년 등장한 TypeScript-first 인증 프레임워크. "Auth.js의 대안"을 표방하며, 자체 DB 기반 세션 관리 + 플러그인 시스템이 핵심. Organization, RBAC, 2FA 등을 플러그인으로 제공.
1. DB 이중화 문제
Better Auth는 user, session, account, organization, member 테이블을 자체 생성. 현재 Spring 백엔드에 이미 유저/조직 테이블이 있거나 만들 예정이라면 동일 데이터가 두 곳에 존재하게 됨.
2. 인증 주체 혼란
현재: Spring 백엔드가 POST /api/auth/authenticate로 토큰 발급 (인증 주체 = 백엔드). Better Auth 도입 시: Next.js가 인증 주체가 됨. 백엔드의 인증 엔드포인트를 Better Auth로 대체할지, 병행할지 결정 필요.
3. "인증 DB는 Spring DB 하나로 통합" 제약
Better Auth가 Spring DB에 직접 연결해야 함. Prisma/Drizzle adapter로 가능하지만, Spring이 관리하는 DB에 Next.js 앱이 직접 테이블을 만들고 쓰는 구조가 됨. 백엔드팀 합의 필수.
Better Auth를 도입하려면 다음 중 하나를 선택해야 합니다:
옵션 1: Spring DB에 Better Auth 테이블 추가 허용. Better Auth가 인증/세션/조직을 관리하고, Spring은 비즈니스 로직만 담당. Spring의 /api/auth/authenticate 제거.
옵션 2: Better Auth 도입하지 않음. 인증/조직/역할은 Spring에서 관리. 프론트는 NextAuth로 세션만 관리.
옵션 1은 인증 주체가 프론트(Next.js)로 이동하는 것을 의미. 모바일 앱 추가 시에도 Better Auth API를 통해야 함.
보안 수준: 토큰이 JS에 노출되지만, B2B 내부 툴에서 XSS 발생 확률은 낮음. CSRF는 Bearer 헤더 사용으로 안전.
변경 비용: 없음. 현재 코드 그대로.
// src/app/api/proxy/[...path]/route.ts
import { getServerSession } from 'next-auth'
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
import { NextResponse } from 'next/server'
const BACKEND = process.env.API_BASE_URL!
async function proxy(
req: Request,
{ params }: { params: { path: string[] } }
) {
const session = await getServerSession(authOptions)
if (!session?.user?.token) {
return NextResponse.json(
{ message: 'Unauthorized' },
{ status: 401 }
)
}
const path = params.path.join('/')
const search = new URL(req.url).search
const url = `${BACKEND}/${path}${search}`
const headers: Record<string, string> = {
Authorization: `Bearer ${session.user.token}`,
}
const contentType = req.headers.get('content-type') ?? ''
if (!contentType.includes('multipart')) {
headers['Content-Type'] = 'application/json'
}
const body = ['GET','HEAD'].includes(req.method)
? undefined
: contentType.includes('multipart')
? await req.formData()
: await req.text()
const res = await fetch(url, {
method: req.method,
headers,
body,
})
const data = await res.json().catch(() => null)
return NextResponse.json(data, { status: res.status })
}
export const GET = proxy
export const POST = proxy
export const PATCH = proxy
export const PUT = proxy
export const DELETE = proxy
/api/proxy/*setAuthToken, clearAuthToken, AuthTokenSync 제거session.user.token 클라이언트 노출 제거모든 API 요청이 Next.js 서버를 거치므로 ~10-50ms hop이 추가됨. 같은 인프라(같은 VPC, 같은 리전) 내라면 ~5-15ms 수준. B2B SaaS에서 이 정도는 체감 차이 없음. Vercel 배포 시에도 서버리스 함수 cold start만 주의하면 됨.
| 기준 | 현행 유지 | BFF 프록시 추가 |
|---|---|---|
| 토큰 JS 노출 | YES | NO |
| XSS 방어 | 부분적 | 완전 |
| CSRF 방어 | 완전 | 완전 |
| 백엔드 변경 | 없음 | 없음 |
| 지연 추가 | 없음 | ~10-50ms |
| 코드 삭제량 | - | setAuthToken, clearAuthToken, AuthTokenSync |
| 코드 추가량 | - | catch-all Route Handler 1개 (~50줄) |
| 전환 기간 | - | ~3-5일 |
| 모바일 확장 | ✅ 직접 호출 | ✅ 직접 호출 (BFF 무관) |
nextjs.org/docs/app/guides/backend-for-frontend). Auth.js v5도 서버 사이드에서만 세션에 접근하는 것을 권장. OWASP는 "세션 토큰을 JS에 노출하지 말 것"을 명시.
Deallo는 B2B SaaS로 Organization(조직) > Member(구성원) > Role(Admin, Finance, Member) 구조를 계획 중. 이를 인증 라이브러리에서 처리할지, 백엔드에서 처리할지가 분기점.
organization, member 테이블 자동 생성useActiveOrganization()로 현재 조직 접근// Spring 백엔드가 토큰에 role 정보를 포함한다고 가정
// NextAuth JWT callback에서 role을 세션에 추가
// auth/[...nextauth]/route.ts
callbacks: {
jwt({ token, user }) {
if (user) {
token.id = user.id
token.role = user.role // "ADMIN" | "FINANCE" | "MEMBER"
token.organizationId = user.organizationId
token.backendToken = user.token // BFF에서만 사용, 클라이언트 미노출
}
return token
},
session({ session, token }) {
session.user.id = token.id
session.user.role = token.role
session.user.organizationId = token.organizationId
// ⚠️ backendToken은 여기서 노출하지 않음 (BFF 프록시에서만 사용)
return session
},
}
// 미들웨어에서 페이지 접근 제어
// middleware.ts
export default withAuth(
function middleware(req) {
const { role } = req.nextauth.token
const path = req.nextUrl.pathname
// Finance 페이지는 ADMIN, FINANCE만
if (path.startsWith('/finance') && !['ADMIN','FINANCE'].includes(role)) {
return NextResponse.redirect(new URL('/unauthorized', req.url))
}
},
{ callbacks: { authorized: ({ token }) => !!token } }
)
아래 의사결정 트리를 따라가면 우리 상황에 맞는 선택지를 찾을 수 있습니다.
→ Spring 백엔드 (현재 구조, 백엔드가 인증 엔드포인트 소유)
이 경우 Better Auth의 자체 DB 관리는 오버헤드. NextAuth가 "세션 프록시" 역할만 하면 충분.
| 선택지 | 예상 기간 | 가능 여부 |
|---|---|---|
| A. NextAuth v4 유지 + BFF 추가 | 3~5일 | 가능 |
| B. Auth.js v5 전환 | 1~2주 | 빠듯 |
| C. Better Auth 전환 | 3~4주 | 불가 |
→ 권장: YES (BFF 프록시로 해결)
B2B 내부 툴이라 긴급하지 않지만, OWASP 권고 준수 + 향후 SOC2/ISO27001 인증 대비. catch-all Route Handler 하나로 해결되므로 비용 대비 효과 높음.
| 항목 | 결정 | 이유 |
|---|---|---|
| 인증 라이브러리 | NextAuth v4 유지 | 동작 중인 코드 교체 불필요, v5 아직 불안정, Better Auth는 DB 협의 필요 |
| 아키텍처 | BFF 프록시 추가 | 토큰 JS 노출 제거, 백엔드 변경 0, 3~5일 전환 |
| Organization/Role | 백엔드에서 관리 | 단일 DB, Spring이 비즈니스 로직 주체 |
| 프론트 역할 처리 | 세션에 role 포함 + 미들웨어 제어 | backendToken은 서버에만, role은 세션에 노출 |
Better Auth는 Org/RBAC/2FA 빌트인이 매력적이므로, 백엔드팀과 DB 통합 방안이 합의되면 중기(1~3개월)에 전환 검토. 그때 Auth.js v5도 안정화되어 있을 가능성 높으므로 함께 재평가.
/api/proxy/[...path]/route.ts catch-all Route Handler 생성/api/proxy로 변경setAuthToken, clearAuthToken, getAuthToken 제거AuthTokenSync 컴포넌트 단순화 (토큰 세팅 로직 제거)token 필드 클라이언트 노출 제거auth-token 쿠키 fallback 코드 제거withCredentials / credentials: 'include' 설정 정리