Under the Hood of NextAuth.js: A Scalable Authentication System
Daniel Hayes
Full-Stack Engineer ยท Leapcell

Source Code Analysis of Next-Auth: A Powerful and Flexible Authentication Solution
Introduction
Next-Auth is a powerful and flexible authentication library that provides convenient authentication functions for Next.js applications and other React projects. It supports multiple authentication methods and has a reasonable division of the source code structure, making it easy for developers to understand and expand. This article will deeply analyze the source code structure and key functions of Next-Auth.
Introduction to the Directory
The core source code of Next-Auth is located in the packages/next-auth/src
directory. The following is the main structure of this directory:
client
: It mainly encapsulates thefetch
method and implements a broadcast event mechanism to listen for changes inlocalStorage
.core
: It contains the main business logic, and the APIs and pages of/api/auth/xxx
are defined here.jwt
: It provides encryption and decryption methods for JWT (JSON Web Token) and is used to handle token-related operations in authentication.next
: It defines the middleware in Next.js and provides specific support for Next.js applications.providers
: It provides the default configurations of various authentication methods, making it convenient for developers to integrate different authentication providers.react
: It provides front-end methods such asuseSession
andgetToken
for React applications, which are used to obtain and update user session information.utils
: It defines some auxiliary methods, such as parsing routes and merging data, to help handle some common tasks.
In addition, there are index.ts
and middleware.ts
files, which play important roles in the initialization of the entire library and middleware processing.
Analysis of the client Directory
The client
directory mainly provides two key functions:
Fetch
Encapsulation: It encapsulates thefetch
method of network requests. All operations involving network requests will call this encapsulated method, which is convenient for unified management and processing of network request-related logic.- Broadcast Event Mechanism: It realizes communication between different tabs or windows by listening to changes in
localStorage
. The code is as follows:
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 }
Currently, the main listener of this broadcast event is the SessionProvider
in React. When a change in localStorage
is detected, it will trigger the __NEXTAUTH._getSession()
method defined in the SessionProvider
. This method is used to request the /api/auth/session
API to obtain the user session object. The __NEXTAUTH._getSession()
method has the following calling methods:
__NEXTAUTH._getSession()
: It is used when called for the first time to initialize the retrieval of session information.__NEXTAUTH._getSession({ event: "storage" })
: It is called when other tabs or windows send messages to update the session to avoid circular updates.__NEXTAUTH._getSession({ event: "visibilitychange" })
: It is triggered when the tab is activated to check whether the session needs to be updated.__NEXTAUTH._getSession({ event: "poll" })
: It is used to poll and refresh the session to ensure the real-time nature of the session information.
The following is the specific implementation of the __NEXTAUTH._getSession()
method:
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 = () => {} } }, [])
Analysis of the react Directory
The react
directory provides a series of practical methods and components for front-end React projects:
SessionProvider
Component: It is usually wrapped around the outermost layer of the entire application to provide a session object for the application, ensuring that session information can be accessed throughout the application.useSession()
hook Function: It is used to consume the session object provided by theSessionProvider
. The type definition of the session object is as follows:
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()
Function: It triggers the login process. If it is an OAuth login, it will send a POST request to/auth/signin/{provider}
for authentication.signOut()
Function: It accesses the/auth/signout
endpoint and will also broadcast an event to notify other browser tabs to achieve the function of logging out simultaneously.getSession()
Function: It is used to obtain the user session object, making it convenient to obtain the user's authentication information in the front-end application.getCsrfToken()
Function: It obtains the anti-cross-site request forgery (XSS) token, which needs to be added to the request body insignIn
,signOut
, andSessionProvider
to enhance security.
Analysis of the next Directory
The next
package is specifically designed for Next.js applications, which contains the entry NextAuth()
method of the entire package. We focus on the code of the NextAuthApiHandler()
branch:
- Request Conversion: It converts the requests of Next.js into the internal request data structure, mainly parses out information such as
action
,cookie
, andhttpmethod
, and is implemented using thetoInternalRequest()
method. - Initialization Operations: It calls the
init()
method, which includes initializing options, handling CSRF tokens (creating or validating), and handling callback URLs (reading from query parameters or cookies). When handling callback URLs, it will call thecallbacks.redirect()
method to perform corresponding processing according to different scenarios (first entry or callback return). - SessionStore Construction: It constructs the
SessionStore
object, which is used to manage theSessionToken
cookie. Since the size of the cookie may be too large, it will be divided into multiple cookies for storage, such asxx.0
,xx.1
,xx.2
, etc. - Request Processing Branches: According to the
httpmethod
, it is divided into two branches,get
andpost
:get
Request: It defines static pages such assignIn
,signOut
,error
, andveryrequest
. If there are custom pages, it will be redirected to the custom pages. In addition, there are interfaces such asproviders
,session
,csrf
, andcallback
, which are used to provide session and token information to the front-end JavaScript, or update tokens and cookies.post
Request: First, it validates the CSRF token by comparing the token in the request body with the token in the cookie to prevent cross-site attacks. ForsignIn
andsignOut
operations, it will prepare cookies and jump to the OAuth site;callback
is used to handle the callback after OAuth authentication is successful; theSession
interface is used for the front-end JavaScript to obtain or update the session object.
Analysis of the jwt Directory
The code related to cookie encryption in Next-Auth is located in the jwt
directory. The encryption key comes from the process.env.NEXTAUTH_SECRET
environment variable, and the following method is used for encryption (the salt is an empty string):
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 ) }
The encrypted key is used to encrypt and sign the token to generate a cookie. The specific encoding and decoding methods are as follows:
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 }
Conclusion
Next-Auth provides powerful and flexible authentication functions through a reasonable division of the source code structure. Whether it is the encapsulation of network requests, session management, support for multiple authentication methods, or considerations for security (such as CSRF protection and JWT encryption), it reflects the excellence of its design. Developers can deeply understand and expand the source code of Next-Auth according to their own needs to meet the authentication requirements of different projects.
Leapcell: The Best of Serverless Web Hosting
Finally, I would like to recommend a platform that is most suitable for deploying services: Leapcell
๐ Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
๐ Deploy Unlimited Projects for Free
Only pay for what you useโno requests, no charges.
โก Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
๐ Explore Our Documentation
๐น Follow us on Twitter: @LeapcellHQ