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
ディレクトリは、主に2つの主要な機能を提供します。
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の処理(クエリパラメータまたはCookieからの読み取り)を含む
init()
メソッドを呼び出します。コールバックURLを処理する際、callbacks.redirect()
メソッドを呼び出して、さまざまなシナリオ(初回エントリまたはコールバックリターン)に応じて対応する処理を実行します。 - SessionStoreの構築:
SessionStore
オブジェクトを構築します。これは、SessionToken
Cookieを管理するために使用されます。Cookieのサイズが大きすぎる可能性があるため、xx.0
、xx.1
、xx.2
などのように、複数のCookieに分割して保存されます。 - リクエスト処理分岐:
httpmethod
に応じて、get
とpost
の2つのブランチに分割されます。get
リクエスト:signIn
、signOut
、error
、veryrequest
などの静的ページを定義します。カスタムページがある場合は、カスタムページにリダイレクトされます。さらに、providers
、session
、csrf
、callback
などのインターフェースがあり、フロントエンドJavaScriptにセッションおよびトークン情報を提供したり、トークンとCookieを更新するために使用されたりします。post
リクエスト: まず、リクエスト本文のトークンとCookieのトークンを比較してCSRFトークンを検証し、クロスサイト攻撃を防ぎます。signIn
およびsignOut
操作の場合、Cookieを準備してOAuthサイトにジャンプします。callback
は、OAuth認証が成功した後のコールバックを処理するために使用されます。Session
インターフェースは、フロントエンドJavaScriptがセッションオブジェクトを取得または更新するために使用されます。
jwtディレクトリの分析
Next-AuthのCookie暗号化に関連するコードは、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 ) }
暗号化されたキーは、トークンを暗号化および署名してCookieを生成するために使用されます。具体的なエンコードおよびデコード方法は次のとおりです。
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:最高のサーバーレスWebホスティング
最後に、サービスのデプロイに最適なプラットフォームである**Leapcell**をお勧めします。
🚀 お気に入りの言語で構築
JavaScript、Python、Go、またはRustで簡単に開発できます。
🌍 無制限のプロジェクトを無料でデプロイ
使用量に応じて料金を支払うだけで、リクエストや料金はかかりません。
⚡ 従量課金制、隠れたコストなし
アイドル料金はなく、シームレスなスケーラビリティのみです。
🔹 Twitterでフォローしてください:@LeapcellHQ