Next-Auth 소스 코드 분석: 강력하고 유연한 인증 솔루션
Emily Parker
Product Engineer · Leapcell

Next-Auth 소스 코드 분석: 강력하고 유연한 인증 솔루션
소개
Next-Auth는 강력하고 유연한 인증 라이브러리로, Next.js 애플리케이션 및 기타 React 프로젝트에 편리한 인증 기능을 제공합니다. 다양한 인증 방법을 지원하며 소스 코드 구조가 합리적으로 분할되어 개발자가 이해하고 확장하기 쉽습니다. 이 기사에서는 Next-Auth의 소스 코드 구조와 핵심 기능을 심층적으로 분석합니다.
디렉토리 소개
Next-Auth의 핵심 소스 코드는 packages/next-auth/src
디렉토리에 있습니다. 다음은 이 디렉토리의 주요 구조입니다.
client
: 주로fetch
메서드를 캡슐화하고localStorage
의 변경 사항을 수신하는 브로드캐스트 이벤트 메커니즘을 구현합니다.core
: 주요 비즈니스 로직을 포함하며/api/auth/xxx
의 API 및 페이지가 여기에 정의되어 있습니다.jwt
: JWT(JSON Web Token)에 대한 암호화 및 해독 메서드를 제공하며 인증에서 토큰 관련 작업을 처리하는 데 사용됩니다.next
: Next.js의 미들웨어를 정의하고 Next.js 애플리케이션에 대한 특정 지원을 제공합니다.providers
: 다양한 인증 방법의 기본 구성을 제공하여 개발자가 다양한 인증 제공자를 통합하는 데 편리합니다.react
: React 애플리케이션을 위한useSession
및getToken
과 같은 프런트 엔드 메서드를 제공하며 사용자 세션 정보를 가져오고 업데이트하는 데 사용됩니다.utils
: 경로 구문 분석 및 데이터 병합과 같은 일부 보조 메서드를 정의하여 몇 가지 일반적인 작업을 처리하는 데 도움이 됩니다.
또한 전체 라이브러리의 초기화 및 미들웨어 처리에서 중요한 역할을 하는 index.ts
및 middleware.ts
파일이 있습니다.
client 디렉토리 분석
client
디렉토리는 주로 두 가지 핵심 기능을 제공합니다.
Fetch
캡슐화: 네트워크 요청의fetch
메서드를 캡슐화합니다. 네트워크 요청과 관련된 모든 작업은 이 캡슐화된 메서드를 호출하여 네트워크 요청 관련 로직을 통합 관리하고 처리하는 데 편리합니다.- 브로드캐스트 이벤트 메커니즘:
localStorage
의 변경 사항을 수신하여 서로 다른 탭 또는 창 간의 통신을 실현합니다. 코드는 다음과 같습니다.
export function BroadcastChannel(name = "nextauth.message") { return { /** Get notifications from other tabs/windows */ receive(onReceive: (message: BroadcastMessage) => void) { const handler = (event: StorageEvent) => { if (event.key!== name) return const message: BroadcastMessage = JSON.parse(event.newValue?? "{}") if (message?.event!== "session" ||!message?.data) return onReceive(message) } window.addEventListener("storage", handler) return () => window.removeEventListener("storage", handler) }, /** Notify other tabs/windows */ post(message: Record<string, unknown>) { if (typeof window === "undefined") return try { localStorage.setItem( name, JSON.stringify({...message, timestamp: now() }) ) } catch { /** * The localStorage API is not always available. For example, it does not work in private mode before Safari 11. * If an error occurs, the notification will be simply discarded. */ } }, } } export interface BroadcastMessage { event?: "session" data?: { trigger?: "signout" | "getSession" } clientId: string timestamp: number }
현재 이 브로드캐스트 이벤트의 주요 리스너는 React의 SessionProvider
입니다. localStorage
의 변경 사항이 감지되면 SessionProvider
에 정의된 __NEXTAUTH._getSession()
메서드를 트리거합니다. 이 메서드는 /api/auth/session
API를 요청하여 사용자 세션 객체를 가져오는 데 사용됩니다. __NEXTAUTH._getSession()
메서드에는 다음과 같은 호출 방법이 있습니다.
__NEXTAUTH._getSession()
: 세션 정보 검색을 초기화하기 위해 처음 호출될 때 사용됩니다.__NEXTAUTH._getSession({ event: "storage" })
: 순환 업데이트를 방지하기 위해 다른 탭이나 창에서 세션을 업데이트하기 위해 메시지를 보낼 때 호출됩니다.__NEXTAUTH._getSession({ event: "visibilitychange" })
: 탭이 활성화되었을 때 세션을 업데이트해야 하는지 확인하기 위해 트리거됩니다.__NEXTAUTH._getSession({ event: "poll" })
: 세션 정보의 실시간 특성을 보장하기 위해 세션을 폴링하고 새로 고치는 데 사용됩니다.
다음은 __NEXTAUTH._getSession()
메서드의 구체적인 구현입니다.
React.useEffect(() => { __NEXTAUTH._getSession = async ({ event } = {}) => { try { const storageEvent = event === "storage" // If there is no client session yet, or there is an event from other tabs/windows, we should always update if (storageEvent || __NEXTAUTH._session === undefined) { __NEXTAUTH._lastSync = now() __NEXTAUTH._session = await getSession({ broadcast:!storageEvent, }) setSession(__NEXTAUTH._session) return } if ( // If the session expiration time is not defined, it is okay to use the existing value before the event triggers an update !event || // If there is no session on the client, we don't need to call the server to check (if logged in through other tabs/windows, it will come in the form of a "storage" event) __NEXTAUTH._session === null || // If the client session has not expired yet, exit early now() < __NEXTAUTH._lastSync ) { return } // An event has occurred or the session has expired, update the client session __NEXTAUTH._lastSync = now() __NEXTAUTH._session = await getSession() setSession(__NEXTAUTH._session) } catch (error) { logger.error("CLIENT_SESSION_ERROR", error as Error) } finally { setLoading(false) } } __NEXTAUTH._getSession() return () => { __NEXTAUTH._lastSync = 0 __NEXTAUTH._session = undefined __NEXTAUTH._getSession = () => {} } }, [])
react 디렉토리 분석
react
디렉토리는 프런트 엔드 React 프로젝트를 위한 일련의 실용적인 메서드와 구성 요소를 제공합니다.
SessionProvider
구성 요소: 일반적으로 애플리케이션 전체에서 세션 정보에 접근할 수 있도록 애플리케이션 전체의 맨 바깥쪽 레이어에 래핑되어 세션 객체를 제공합니다.useSession()
훅 함수:SessionProvider
가 제공하는 세션 객체를 사용하는 데 사용됩니다. 세션 객체의 유형 정의는 다음과 같습니다.
export type SessionContextValue<R extends boolean = false> = R extends true ? | { update: UpdateSession; data: Session; status: "authenticated" } | { update: UpdateSession; data: null; status: "loading" } : | { update: UpdateSession; data: Session; status: "authenticated" } | { update: UpdateSession data: null status: "unauthenticated" | "loading" }
signIn()
함수: 로그인 프로세스를 트리거합니다. OAuth 로그인인 경우 인증을 위해/auth/signin/{provider}
에 POST 요청을 보냅니다.signOut()
함수:/auth/signout
엔드포인트에 접근하고 다른 브라우저 탭에 동시에 로그아웃 기능을 구현하기 위해 이벤트를 브로드캐스트합니다.getSession()
함수: 사용자 세션 객체를 가져오는 데 사용되어 프런트 엔드 애플리케이션에서 사용자의 인증 정보를 편리하게 가져올 수 있습니다.getCsrfToken()
함수: 교차 사이트 요청 위조(XSS) 방지 토큰을 획득합니다. 보안 강화를 위해signIn
,signOut
및SessionProvider
에서 요청 본문에 추가해야 합니다.
next 디렉토리 분석
next
패키지는 Next.js 애플리케이션을 위해 특별히 설계되었으며 전체 패키지의 진입점인 NextAuth()
메서드가 포함되어 있습니다. NextAuthApiHandler()
분기의 코드에 집중합니다.
- 요청 변환: Next.js의 요청을 내부 요청 데이터 구조로 변환하고 주로
action
,cookie
및httpmethod
와 같은 정보를 파싱하여toInternalRequest()
메서드를 사용하여 구현합니다. - 초기화 작업: 옵션 초기화, CSRF 토큰 처리(생성 또는 유효성 검사) 및 콜백 URL 처리(쿼리 매개변수 또는 쿠키에서 읽기)를 포함하는
init()
메서드를 호출합니다. 콜백 URL을 처리할 때callbacks.redirect()
메서드를 호출하여 다양한 시나리오(첫 번째 진입 또는 콜백 반환)에 따라 해당 처리를 수행합니다. - SessionStore 구성:
SessionToken
쿠키를 관리하는 데 사용되는SessionStore
객체를 구성합니다. 쿠키 크기가 너무 클 수 있으므로xx.0
,xx.1
,xx.2
등과 같이 여러 쿠키로 나누어 저장합니다. - 요청 처리 분기:
httpmethod
에 따라get
과post
의 두 가지 분기로 나뉩니다.get
요청:signIn
,signOut
,error
및veryrequest
와 같은 정적 페이지를 정의합니다. 사용자 지정 페이지가 있는 경우 사용자 지정 페이지로 리디렉션됩니다. 또한 프런트 엔드 JavaScript에 세션 및 토큰 정보를 제공하거나 토큰과 쿠키를 업데이트하는 데 사용되는providers
,session
,csrf
및callback
과 같은 인터페이스가 있습니다.post
요청: 먼저 교차 사이트 공격을 방지하기 위해 요청 본문의 토큰과 쿠키의 토큰을 비교하여 CSRF 토큰의 유효성을 검사합니다.signIn
및signOut
작업의 경우 쿠키를 준비하고 OAuth 사이트로 이동합니다.callback
은 OAuth 인증이 성공한 후 콜백을 처리하는 데 사용됩니다.Session
인터페이스는 프런트 엔드 JavaScript가 세션 객체를 가져오거나 업데이트하는 데 사용됩니다.
jwt 디렉토리 분석
Next-Auth의 쿠키 암호화와 관련된 코드는 jwt
디렉토리에 있습니다. 암호화 키는 process.env.NEXTAUTH_SECRET
환경 변수에서 가져오며 다음 메서드가 암호화에 사용됩니다(솔트는 빈 문자열임).
import hkdf from "@panva/hkdf" async function getDerivedEncryptionKey( keyMaterial: string | Buffer, salt: string ) { return await hkdf( "sha256", keyMaterial, salt, `NextAuth.js Generated Encryption Key${salt? ` (${salt})` : ""}`, 32 ) }
암호화된 키는 토큰을 암호화하고 서명하여 쿠키를 생성하는 데 사용됩니다. 구체적인 인코딩 및 디코딩 방법은 다음과 같습니다.
import { EncryptJWT, jwtDecrypt } from "jose"; export async function encode(params: JWTEncodeParams) { /** @note An empty `salt` indicates a session token. See {@link JWTEncodeParams.salt}. */ const { token = {}, secret, maxAge = DEFAULT_MAX_AGE, salt = "" } = params const encryptionSecret = await getDerivedEncryptionKey(secret, salt) return await new EncryptJWT(token) .setProtectedHeader({ alg: "dir", enc: "A256GCM" }) .setIssuedAt() .setExpirationTime(now() + maxAge) .setJti(uuid()) .encrypt(encryptionSecret) } /** Decode the JWT issued by Next-Auth.js. */ export async function decode(params: JWTDecodeParams): Promise<JWT | null> { /** @note An empty `salt` indicates a session token. See {@link JWTDecodeParams.salt}. */ const { token, secret, salt = "" } = params if (!token) return null const encryptionSecret = await getDerivedEncryptionKey(secret, salt) const { payload } = await jwtDecrypt(token, encryptionSecret, { clockTolerance: 15, }) return payload }
결론
Next-Auth는 소스 코드 구조의 합리적인 분할을 통해 강력하고 유연한 인증 기능을 제공합니다. 네트워크 요청 캡슐화, 세션 관리, 다중 인증 방식 지원, 보안 고려 사항(예: CSRF 보호 및 JWT 암호화) 등 설계의 우수성을 반영합니다. 개발자는 자신의 필요에 따라 Next-Auth의 소스 코드를 깊이 이해하고 확장하여 다양한 프로젝트의 인증 요구 사항을 충족할 수 있습니다.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로, 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 추천하고 싶습니다.
🚀 선호하는 언어를 사용하여 빌드
JavaScript, Python, Go 또는 Rust로 손쉽게 개발하십시오.
🌍 무제한 프로젝트를 무료로 배포
사용한 만큼만 지불하십시오. 요청도 없고 요금도 없습니다.
⚡ 사용량에 따라 지불, 숨겨진 비용 없음
유휴 요금 없이 원활한 확장성만 제공됩니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ