Skip to content

Add New Authentication Providers.zh CN

cy948 edited this page Feb 23, 2024 · 2 revisions

新身份验证方式开发指南

LobeChat 使用 Auth.js v5 作为外部身份验证服务。Auth.js 是一个开源的身份验证库,它提供了一种简单的方式来实现身份验证和授权功能。本文档将介绍如何使用 Auth.js 来实现新的身份验证方式。

TOC

添加新的身份验证提供者

为了在 LobeChat 中添加新的身份验证提供者(例如添加 Okta),你需要完成以下步骤:

准备工作:查阅官方的提供者列表

首先,你需要查阅 Auth.js 提供者列表 来了解是否你的提供者已经被支持。如果你的提供者已经被支持,你可以直接使用 Auth.js 提供的 SDK 来实现身份验证功能。

接下来我会以 Okta 为例来介绍如何添加新的身份验证提供者

步骤 1: 新增关键代码

打开 src/app/api/auth/next-auth.ts 文件,引入 next-auth/providers/okta

import { NextAuth } from 'next-auth';
import Auth0 from 'next-auth/providers/auth0';
import Okta from 'next-auth/providers/okta';

// 引入 Okta 提供者

新增预定义的服务端配置

// 导入服务器配置
const { OKTA_CLIENT_ID, OKTA_CLIENT_SECRET, OKTA_ISSUER } = getServerConfig();

const nextAuth = NextAuth({
  providers: [
    // ... 其他提供者

    Okta({
      clientId: OKTA_CLIENT_ID,
      clientSecret: OKTA_CLIENT_SECRET,
      issuer: OKTA_ISSUER,
    }),
  ],
});

步骤 2: 更新服务端配置代码

打开 src/config/server/app.ts 文件,在 getAppConfig 函数中新增 Okta 相关的环境变量

export const getAppConfig = () => {
  // ... 其他代码

  return {
    // ... 其他环境变量

    OKTA_CLIENT_ID: process.env.OKTA_CLIENT_ID || '',
    OKTA_CLIENT_SECRET: process.env.OKTA_CLIENT_SECRET || '',
    OKTA_ISSUER: process.env.OKTA_ISSUER || '',
  };
};

步骤 3: 修改前端页面

修改在 src/features/Conversation/Error/OAuthForm.tsxsrc/app/settings/common/Common.tsx 中的 signIn 函数参数

默认为 auth0,你可以将其修改为 okta 以切换到 Okta 提供者,或删除该参数以支持所有已添加的身份验证服务

该值为 Auth.js 提供者 的 id,你可以阅读相应的 next-auth/providers 模块源码以读取默认 ID

步骤 4: 配置环境变量

在部署时新增 Okta 相关的环境变量 OKTA_CLIENT_IDOKTA_CLIENT_SECRETOKTA_ISSUER,并填入相应的值,即可使用

步骤 5: 修改服务端用户信息处理逻辑

在前端获取用户信息

在前端页面中使用 useOAuthSession() 方法获取后端返回的用户信息 user

import { useOAuthSession } from '@/hooks/useOAuthSession';

const { user, isOAuthLoggedIn } = useOAuthSession();

默认的 user 类型为 User,类型定义为:

interface User {
  id?: string;
  name?: string | null;
  email?: string | null;
  image?: string | null;
}

修改用户 id 处理逻辑

user.id 用于标识用户。当引入新身份 OAuth 提供者后,您需要在 src/app/api/auth/next-auth.ts 中处理 OAuth 回调所携带的信息。您需要从中选取用户的 id。在此之前,我们需要了解 Auth.js 的数据处理顺序:

authorize --> jwt --> session

默认情况下,在 jwt --> session 过程中,Auth.js自动根据登陆类型将用户 id 赋值到 account.providerAccountId 中。 如果您需要选取其他值作为用户 id ,您需要实现以下处理逻辑。

  callbacks: {
    async jwt({ token, account, profile }) {
      if (account) {
        // 您可以从 `account` 或 `profile` 中选取其他值
        token.userId = account.providerAccountId;
      }
      return token;
    },
  },

自定义 session 返回

如果您想在 session 中携带更多关于 profileaccount 的信息,根据上面提到的 Auth.js 数据处理顺序,那必须先将该信息复制到 token 上。 示例:把用户头像 URL:profile.picture 添加到session 中:

  callbacks: {
    async jwt({ token, profile, account }) {
      if (profile && account) {
        token.userId = account.providerAccountId;
+       token.avatar = profile.picture;
      }
      return token;
    },
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.userId ?? session.user.id;
+       session.user.avatar = token.avatar;
      }
      return session;
    },
  },

然后补充对新增参数的类型定义:

declare module '@auth/core/jwt' {
  interface JWT {
    // ...
    avatar?: string;
  }
}

declare module 'next-auth' {
  interface User {
    avatar?: string;
  }
}

更多Auth.js内置类型拓展

在处理逻辑中区分多个身份验证提供者

如果您配置了多个身份验证提供者,并且他们的 userId 映射各不相同,可以在 jwt 方法中的 account.provider 参数获取身份提供者的默认 id ,从而进入不同的处理逻辑。

  callbacks: {
    async jwt({ token, profile, account }) {
      if (profile && account) {
        if (account.provider === 'Authing')
          token.userId = account.providerAccountId ?? token.sub;
        else if (acount.provider === 'Okta')
          token.userId = profile.sub ?? token.sub;
        else
          // other providers
      }
      return token;
    },
  }
Clone this wiki locally