Deallo 인증/인가 전환 제안서

2026-03-10 · Next.js 16 (App Router) + Spring 백엔드 · B2B SaaS
대상: 프론트엔드팀 + 백엔드팀 · 전환 목표: 1~2주

Executive Summary

핵심 질문: NextAuth v4를 유지/업그레이드할 것인가, Better Auth로 전환할 것인가? 그리고 BFF 패턴을 도입할 것인가?

현재 상황

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 프록시 추가 중간 라이브러리 변경 없이 보안 강화

💡 권장안: D NextAuth v4 유지 + BFF 프록시 추가

이유: 1~2주 전환 목표에 부합하고, 라이브러리 교체 없이 보안(토큰 JS 노출 제거)을 확보. Auth.js v5는 아직 stable이 아닌 API 변경이 잦고, Better Auth는 DB 스키마 협의가 선행되어야 함. Organization/Role은 백엔드가 이미 관리할 예정이므로 인증 라이브러리에서 담당할 필요 없음.

단, Better Auth의 Org/Role 빌트인이 매력적이라면 C를 중기 로드맵에 배치하는 것도 유효. 이 경우 백엔드팀과 DB 테이블 협의가 선행 조건.

현재 구조 분석

인증 흐름

[로그인] useSignIn() → signIn('credentials') → NextAuth CredentialsProvider.authorize() (서버에서 실행) → POST /api/auth/authenticate → { token } → return { id, username, token } → JWT Callback → token 객체에 저장 → Session Callback → session.user.token으로 클라이언트에 노출 → httpOnly 쿠키에 암호화 저장 [새로고침 후] AuthTokenSync → useSession() → session.user.token 꺼냄 → setAuthToken() → axios 헤더에 세팅 (JS 메모리 노출) [API 요청] 브라우저 → axios + Bearer 헤더 → Spring 백엔드 직접

보안 평가

잘 되어 있는 것
  • Bearer 헤더 사용 → CSRF 원천 차단
  • httpOnly 쿠키로 세션 영속성 확보
  • 401 시 즉시 리디렉트
  • 백엔드 Stateless 유지
⚠️
개선 필요
  • session.user.token으로 토큰 JS 노출 → httpOnly 이점 반감
  • XSS 발생 시 토큰 탈취 가능
  • 레거시 auth-token 쿠키 fallback 미정리
  • AuthTokenSync loading 중 공백 렌더
  • Refresh token 없음 (만료 시 재로그인)
핵심 문제: httpOnly 쿠키에 저장했지만 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로 가능 ✅ 동일 간접적 (자체 인증 우선)

Auth.js v5 주요 변경사항 (NextAuth v4 대비)

Breaking Changes

  • next-auth@auth/nextjs 패키지명 변경 (v5 후기)
  • getServerSession(authOptions)auth() 함수로 통합
  • unstable_getServerSession 제거
  • App Router 기본 지원: auth.ts에서 handlers, auth, signIn, signOut export
  • 미들웨어에서 auth() 직접 사용 (Edge Runtime 호환)
  • JWT callback, Session callback 시그니처 변경

✅ 전환 장점

  • App Router 네이티브 지원
  • 미들웨어에서 세션 직접 접근
  • auth() 하나로 서버 어디서든 세션 확인
  • Edge Runtime 호환
  • 향후 장기 지원 (v4는 유지보수 모드)

❌ 전환 리스크

  • 아직 API가 안정화 중 (릴리즈마다 변경)
  • Credentials Provider 여전히 "실험적" 취급
  • 커뮤니티 마이그레이션 가이드 불완전
  • 전환 중 인증 장애 리스크
  • 1~2주 안에 하기엔 리스크 큼
커뮤니티 온도: Reddit r/nextjs에서 "v5가 stable 되면 전환하겠다"는 의견이 다수. 2025년 말 기준으로도 breaking change가 간헐적. 급한 전환보다는 v4 유지 후 v5 안정화 시 전환이 안전.

Better Auth 심층 분석

Better Auth란?

2024년 등장한 TypeScript-first 인증 프레임워크. "Auth.js의 대안"을 표방하며, 자체 DB 기반 세션 관리 + 플러그인 시스템이 핵심. Organization, RBAC, 2FA 등을 플러그인으로 제공.

아키텍처 차이

[NextAuth] OAuth/Credentials → JWT or DB Session → 쿠키 → 세션 정보만 관리, 비즈니스 로직은 백엔드에 위임 → "인증 미들웨어" 역할 [Better Auth] 자체 user/session/account/org/member 테이블 소유 → 회원가입, 로그인, 세션, 조직, 역할을 모두 자체 관리 → "인증 서버" 역할

⚠️ Deallo 구조와의 충돌 포인트

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 장점

  • Organization + RBAC 빌트인 (Admin/Finance/Member 즉시 구현)
  • 2FA, 이메일 인증, 매직링크 플러그인
  • TypeScript-first, 타입 안전
  • DB 세션이라 서버에서 즉시 무효화 가능
  • 활발한 커뮤니티 (2024~2025 급성장)

❌ Better Auth 리스크

  • 자체 DB 필수 → Spring DB 통합 협의 필요
  • 기존 인증 흐름 전면 교체
  • npm 다운로드 아직 상대적으로 적음
  • Spring 백엔드의 인증 엔드포인트와 역할 충돌
  • 1~2주 전환 불가능 (최소 3~4주)

🔑 백엔드팀 결정 필요 사항

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를 통해야 함.

인증 아키텍처 비교

현행 구조 (Semi-BFF)

브라우저 ├─ useSession() → session.user.token (JS 메모리 노출) ├─ setAuthToken() → axios Authorization 헤더 └─ axios → Bearer 토큰 → Spring 백엔드 직접

보안 수준: 토큰이 JS에 노출되지만, B2B 내부 툴에서 XSS 발생 확률은 낮음. CSRF는 Bearer 헤더 사용으로 안전.

변경 비용: 없음. 현재 코드 그대로.

BFF 프록시 추가 (권장)

브라우저 ├─ 쿠키만 자동 전송 (토큰을 모름) └─ fetch/ky → /api/proxy/[...path] Next.js 서버 (BFF) ├─ getServerSession() → session.user.token (서버에서만 접근) └─ Authorization: Bearer {token} 붙여서 → Spring 백엔드 모바일 / B2B 서버 └─ Spring 백엔드 직접 호출 (Bearer 토큰) → 영향 없음

catch-all 프록시 구현

// 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 호출 경로: 백엔드 직접 → /api/proxy/*
  • setAuthToken, clearAuthToken, AuthTokenSync 제거
  • session.user.token 클라이언트 노출 제거
  • Session callback에서 token 필드 제외

변경 안 됨

  • NextAuth v4 그대로 유지
  • Spring 백엔드 코드 변경 없음
  • 로그인 흐름 (CredentialsProvider) 동일
  • TanStack Query 훅 구조 동일 (URL만 변경)
  • 백엔드 Stateless Bearer 그대로
핵심 이점: 라이브러리 교체 없이 catch-all Route Handler 하나 추가 + API prefixUrl 변경만으로 토큰 JS 노출을 완전 제거. 백엔드 코드 변경 0.

지연(Latency) 영향

모든 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 무관)
참고 레퍼런스: Next.js 공식 문서에서 BFF 패턴을 공식 가이드로 제공 중 (nextjs.org/docs/app/guides/backend-for-frontend). Auth.js v5도 서버 사이드에서만 세션에 접근하는 것을 권장. OWASP는 "세션 토큰을 JS에 노출하지 말 것"을 명시.

Organization / Role 체계

Deallo는 B2B SaaS로 Organization(조직) > Member(구성원) > Role(Admin, Finance, Member) 구조를 계획 중. 이를 인증 라이브러리에서 처리할지, 백엔드에서 처리할지가 분기점.

🏢
Better Auth로 처리 시
  • Organization 플러그인으로 조직 CRUD 빌트인
  • Member 초대, 역할 할당 API 즉시 사용 가능
  • organization, member 테이블 자동 생성
  • 프론트에서 useActiveOrganization()로 현재 조직 접근
단, Spring 백엔드에도 조직/멤버 로직이 필요하다면 데이터 이중화. Better Auth DB ↔ Spring DB 동기화 문제 발생.
🔧
백엔드에서 처리 시 (권장)
  • Spring 백엔드가 Organization, Member, Role 관리
  • JWT 또는 API 응답에 역할 정보 포함
  • 프론트는 역할 정보를 받아서 UI 분기만 처리
  • 단일 DB, 단일 진실의 원천(Single Source of Truth)
현재 Deallo 구조(Spring이 비즈니스 로직 담당)와 일치. 추가 DB 협의 불필요.

프론트엔드 역할 기반 접근 제어 (백엔드 처리 시)

// 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 } }
)

의사결정 가이드

아래 의사결정 트리를 따라가면 우리 상황에 맞는 선택지를 찾을 수 있습니다.

Q1. 인증/조직 데이터의 주체는?

→ Spring 백엔드 (현재 구조, 백엔드가 인증 엔드포인트 소유)

이 경우 Better Auth의 자체 DB 관리는 오버헤드. NextAuth가 "세션 프록시" 역할만 하면 충분.

Q2. 1~2주 안에 전환 가능한가?

선택지 예상 기간 가능 여부
A. NextAuth v4 유지 + BFF 추가 3~5일 가능
B. Auth.js v5 전환 1~2주 빠듯
C. Better Auth 전환 3~4주 불가

Q3. 토큰 JS 노출을 제거해야 하는가?

→ 권장: 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도 안정화되어 있을 가능성 높으므로 함께 재평가.

실행 계획 (1~2주)

Phase 1: BFF 프록시 추가 (Day 1~3)

Phase 2: 클라이언트 코드 정리 (Day 3~5)

Phase 3: 테스트 및 검증 (Day 5~7)

Phase 4 (백엔드 협의 후): Role 연동

백엔드팀 액션 아이템: Phase 1~3 동안 백엔드 변경사항 없음. Phase 4에서 토큰에 role/organizationId 포함 여부만 협의. Better Auth 도입 시에는 DB 테이블 추가 협의 필요 (현 시점에서는 보류 권장).