首页 > 编程语言 >Next-Auth 源码解析

Next-Auth 源码解析

时间:2024-04-22 23:12:15浏览次数:28  
标签:__ NEXTAUTH event Auth Next token session ._ 源码

Next-Auth 源码解析

简单介绍一下Next-Auth 源码的结构

目录简介

我们看packages/next-auth/src,这个目录下面是根目录,我们会看到下面的结构

--src
  -- client  // 这个里面主要是封装了fetch 这个方法
  -- core    // 这个是主要的方法,/api/auth/xxx 的api 及页面都是在这个里面定义的
  -- jwt    // 这个里面主要提供了jwt token 加密解密的方法
  -- next   // 这个主要是定义了nextjs中的middleware 的定义
  -- providers  // 提供了各种认证方法的默认配置
  -- react    // 这个是给react 使用的,提供了useSession/getToken等前端的获取更新session 的方法
  -- utils   // 这个是定义了一些辅助方法,解析路由,合并数据等。
  index.ts
  middleware.ts

我们先来看client 这个目录,这个目录里面主要提供了两个东西,一个是对Fetch 的封装,凡是由网络请求的都会调用这个封装。另一个是封装了一个广播事件的东西。监听了localstorage里面的改动。

export function BroadcastChannel(name = "nextauth.message") {
  return {
    /** Get notified by 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 isn't always available.
         * It won't work in private mode prior to Safari 11 for example.
         * Notifications are simply dropped if an error is encountered.
         */
      }
    },
  }
}

export interface BroadcastMessage {
  event?: "session"
  data?: { trigger?: "signout" | "getSession" }
  clientId: string
  timestamp: number
}

这个广播事件,目前的监听者就是在React里面的SessionProvider,它会触发在SessionProvider里面定义的__NEXTAUTH._getSession()方法,这个方法的调用参数是__NEXTAUTH._getSession({ event: "storage" })。而这个方法是为了请求/api/auth/session这个API,来获取session对象。就整个代码而言,这个方法由如下几种可能的调用方式

  • __NEXTAUTH._getSession() 这个是第一次调用
  • __NEXTAUTH._getSession({ event: "storage" }) 这个分支是为了避免循环,或者浏览器其他tab 发消息,来更新
  • __NEXTAUTH._getSession({ event: "visibilitychange" }) 这个是tab 被激活的时候触发
  • __NEXTAUTH._getSession({ event: "poll" }) 这个是轮询刷新session
    源码片段如下
React.useEffect(() => {
    __NEXTAUTH._getSession = async ({ event } = {}) => {
      try {
        const storageEvent = event === "storage"
        // We should always update if we don't have a client session yet
        // or if there are events from other tabs/windows
        if (storageEvent || __NEXTAUTH._session === undefined) {
          __NEXTAUTH._lastSync = now()
          __NEXTAUTH._session = await getSession({
            broadcast: !storageEvent,
          })
          setSession(__NEXTAUTH._session)
          return
        }

        if (
          // If there is no time defined for when a session should be considered
          // stale, then it's okay to use the value we have until an event is
          // triggered which updates it
          !event ||
          // If the client doesn't have a session then we don't need to call
          // the server to check if it does (if they have signed in via another
          // tab or window that will come through as a "stroage" event
          // event anyway)
          __NEXTAUTH._session === null ||
          // Bail out early if the client session is not stale yet
          now() < __NEXTAUTH._lastSync
        ) {
          return
        }

        // An event or session staleness occurred, 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 项目使用,具体包括如下

SessionProvider组件,一般套在整个App 的最外面,用于给整个应用提供Session 对象

useSession()hook 函数,这个是用来消费SessionProvider 的Session对象,Session对象的类型如下

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(),这个函数会出发signIn的流程,如果是OAuth,它实际上会去post 到/auth/signin/{provider},

signOut() 这个函数会访问/auth/signout,同时会广播事件,通知其他浏览器的tab,从而实现同时登出。

getSession() 这个是用于获取session对象的

getCsrfToken() 这个是获取xss 防跨站token的,在signIn,signOut,SessionProvider里面必须添加到body里面,

next这个包是专门为nextjs 而准备的,这个目录里面包含了整个包的入口 也就是NextAuth()这个方法。我们主要关注NextAuthApiHandler()这个分支的代码

  • 首先将nextjs 的请求转换成内部的请求数据结构,主要是解析出action, cookie, httpmethod 等,使用的是 toInternalRequest()
  • 接着调用init(),这一步包含初始化options,处理csrfToken(创建或者验证),处理callbackurl(从查询参数里面读取,或者从cookie里面解析),处理callbackurl 的时候会调用我们定义的callbacks.redirect()。这个方法是针对第一次进入,或者回调回来进入的场景,所以有了从查询参数读取,然后保存到cookie, 或者从cookie里面读取,这两种场景,cookie 里面的值是之前写进去的。
  • 构建SessionStore这个对象,主要是用来管理SessionToken这个cookie 的,它支持划分为多个cookie 的情况。由于cookie 的体积过大,会分多个cookie 来存储,会有后缀的 xx.0, xx.1, xx.2 ...
  • 依据httpmethod, 分为get 跟post 两个分支
    • 对于get 请求,分别定义了signIn,signOut,error,veryrequest这些静态页面,如果这些页面提供了自定义的页面,则会重定向到提供的页面上,除了这几个action 之外,还有一些其他的接口,providers,session,csrf,callback,这些主要是用来获取session/token等信息到前端js 中,或者更新token/cookie
    • 对于post 请求,首先一定会验证csrftoken,通过比较body里面的跟cookie里面的csrf token, 来预防跨站。这个里面有个前提是js 在跨站的情况下是拿不到csrf token 的,只有在同域名的情况下才可以拿到。对于signIn,signOut,这个主要是准备cookie 然后跳转到OAuth 站点,callback这个则是为了OAuth 认证通过后会跳回来准备的,Session,这个是给前端js 用于获取session对象或者更新session 对象准备的。其他的就不重要了。

再聊聊这个包对于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
  )
}
  • 通过加密后的密钥用于对token 进行加密签名从而生成cookie.
import { EncryptJWT, jwtDecrypt } from "jose";
export async function encode(params: JWTEncodeParams) {
  /** @note empty `salt` means 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)
}

/** Decodes a NextAuth.js issued JWT. */
export async function decode(params: JWTDecodeParams): Promise<JWT | null> {
  /** @note empty `salt` means 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
}

曾经遇到过clock_Toleranc的问题,可以尝试将源码中的10ms 增大,源码core>lib>oAuth>client.ts


  // allow a 10 second skew
  // See https://github.com/nextauthjs/next-auth/issues/3032
  // and https://github.com/nextauthjs/next-auth/issues/3067
  client[custom.clock_tolerance] = 10

标签:__,NEXTAUTH,event,Auth,Next,token,session,._,源码
From: https://www.cnblogs.com/kongshu-612/p/18151786

相关文章

  • HarmonyOS NEXT应用开发案例—使用弹簧曲线实现抖动动画及手机振动效果案例
    介绍本示例介绍使用vibrator.startVibration方法实现手机振动效果,用animateTo显示动画实现点击后的抖动动画。效果图预览使用说明加载完成后显示登录界面,未勾选协议时点击一键登录按钮会触发手机振动效果和提示文本的抖动动画。实现思路创建一个函数startVibrate()调用v......
  • xxl-job源码解析
    简介:XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。Features:1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行......
  • 这个网站真的太香了!居然可以免费使用AI聊天工具和“智能AI聊天助手”项目源码!
    宝子们,在这个AI爆火的时代,你是否还在因为无法使用ChatGpt而头疼?是否还在寻觅一款国内的好用AI工具呢?好消息!小编花费三个月终于找到了一个可以免费使用AI聊天工具的网站,由于这个网站之前一直在内测阶段,所以就没有给大家分享。刚好,近期这个网站正式上线了。小编今天就来好好跟大......
  • 序列化类源码分析
    序列化类源码分析​ 我们主要带着两个问题取探究反序列化类校验是如何走的局部钩子和全局钩子序列化类实例化分单条和多条,它们实例化得到的对象是不一样的(不同的类)单条和多条的序列化类的实例化​ 首先当我们去查多条和查一条时,会在我们定义的序列化类传入参数many=True/......
  • ElasticSearch 7.17.20本地源码调试
    目录使用本地安装gradle下载相关依赖本地编译本地调试使用本地安装gradle下载相关依赖在gradle安装目录下的init.d目录中,创建初始化脚本init.gradle,添加如下脚本,将其中的maven仓库源全部换成国内镜像allprojects{repositories{defREPOSITORY_URL='http://m......
  • 认证组件及源码分析
    认证组件​ 用于判断用户是否登录简单使用#1.创建一个任意名字的py文件#2.导入认证类fromrest_framework.authenticationimportBaseAuthentication#3.写一个类继承它并且重写authenticate方法classLoginAuth(BaseAuthentication):defauthenticate(self,req......
  • 权限组件及源码分析
    权限组件​ 通过观察APIView的源码,会发现他的里面执行了三个方法self.perform_authentication(request)#认证self.check_permissions(request)#权限self.check_throttles(request)#频率​ 也由此看出,权限是在认证之后执行的权限类的编写写一个类,继承Base......
  • 频率组件及源码分析
    频率组件​ 他的作用是限制接口访问的频率频率类的编写写一个类,继承SimpleRateThrottle重写get_cache_key,返回唯一标识,返回什么就以什么做限制重写类属性rate控制频率fromrest_framework.throttlingimportBaseThrottle,SimpleRateThrottleclassCommonThrottling(S......
  • 全局异常捕获及源码分析
    全局异常捕获​ drf只会捕获属于drf的异常,所以要做到全局异常捕获,还需要手动操作一下。​ 经过对drf异常处理组件的源码分析之后可以得知,其实就是自己定义一个exception_handler函数,然后全局替换一下即可​ 首先要知道,虽然是自己写一个exception_handler函数,但是drf的exception......
  • Django之settings源码分析
    引入查看源码的前提刚开始阅读一些库的源码的时候,最好选一些代码量少的先感受一下看到看不懂的,没有必要去死磕,挑一些看得懂的,再结合网上的一些文献一.django的两个配置文件一个是暴露给用户可以自己自定义的配置文件也就是项目根目录下的settings.py一个是项目默认的配......