Skip to content

Add New Authentication Providers

cy948 edited this page Feb 23, 2024 · 2 revisions

New Authentication Provider Guide

LobeChat uses Auth.js v5 as the external authentication service. Auth.js is an open-source authentication library that provides a simple way to implement authentication and authorization features. This document will introduce how to use Auth.js to implement a new authentication provider.

TOC

Add New Authentication Provider

To add a new authentication provider in LobeChat (for example, adding Okta), you need to follow the steps below:

Pre-requisites: Check the Official Provider List

First, you need to check the Auth.js Provider List to see if your provider is already supported. If yes, you can directly use the SDK provided by Auth.js to implement the authentication feature.

Next, I will use Okta as an example to introduce how to add a new authentication provider.

Step 1: Add Authenticator Core Code

Open the src/app/api/auth/next-auth.ts file and import next-auth/providers/okta.

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

// Import Okta provider

Add the predefined server configuration.

// Import server configuration
const { OKTA_CLIENT_ID, OKTA_CLIENT_SECRET, OKTA_ISSUER } = getServerConfig();

const nextAuth = NextAuth({
  providers: [
    // ... Other providers

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

Step 2: Update Server Configuration Code

Open the src/config/server/app.ts file and add Okta-related environment variables in the getAppConfig function.

export const getAppConfig = () => {
  // ... Other code

  return {
    // ... Other environment variables

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

Step 3: Change Frontend Pages

Modify the signIn function parameter in src/Features/Conversation/Error/OAuthForm.tsx and `src/app/settings/common/Common.tsx

The default is auth0, which you can change to okta to switch to the Okta provider, or remove this parameter to support all added authentication services

This value is the id of the Auth.js provider, and you can read the source code of the corresponding next-auth/providers module to read the default ID.

Step 4: Configure the Environment Variables

Add OKTA_CLIENT_IDOKTA_CLIENT_SECRETOKTA_ISSUER environment variables when you deploy.

Step 5: Modify server-side user information processing logic

Get user information in the frontend

Use the useOAuthSession() method in the frontend page to get the user information user returned by the backend:

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

const { user, isOAuthLoggedIn } = useOAuthSession();

The default type of user is User, and the type definition is:

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

Modify user id handling logic

The user.id is used to identify users. When introducing a new OAuth identity provider, you need to handle the information carried in the OAuth callback in src/app/api/auth/next-auth.ts. You need to select the user's id from this information. Before that, we need to understand the data processing sequence of Auth.js:

authorize --> jwt --> session

By default, in the jwt --> session process, Auth.js will automatically assign the user id to account.providerAccountId based on the login type. If you need to select a different value as the user id, you need to implement the following handling logic:

callbacks: {
  async jwt({ token, account, profile }) {
    if (account) {
      // You can select a different value from `account` or `profile`
      token.userId = account.providerAccountId;
    }
    return token;
  },
},

Customize session return

If you want to carry more information about profile and account in the session, according to the data processing order mentioned above in Auth.js, you must first copy this information to the token. For example, add the user avatar URL profile.picture to the 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;
    },
  },

Then supplement the type definition for the new parameters:

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

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

More built-in type extensions in Auth.js

Differentiate multiple authentication providers in the processing logic

If you have configured multiple authentication providers and their userId mappings are different, you can use the account.provider parameter in the jwt method to get the default id of the identity provider and enter different processing logic.

  callbacks: {
    async jwt({ token, profile, account }) {
      if (profile && account) {
        if (account.provider === 'authing')
          token.userId = account.providerAccountId ?? token.sub;
        else if (acount.provider === 'auth0')
          token.userId = profile.sub ?? token.sub;
        else
          // other providers
      }
      return token;
    },
  }

Now, you can use Okta as your provider to implement the authentication feature in LobeChat.

Clone this wiki locally