Building an Account switcher with NextJS and Next Auth

oreoluwabs

Oreoluwa Bimbo-Salami

Posted on June 3, 2023

Building an Account switcher with NextJS and Next Auth

This article delves into the step-by-step process of integrating NextAuth into a Next.js application, configuring authentication providers, and implementing the account switcher functionality.

Prerequisites

  • Basic understanding of JavaScript and React(Next.js)
  • Familiarity with NextAuth
  • NodeJS v16 or later

Select Component with account switcher

Getting started

To get started, we would first need to initialise a Next.js project and install the necessary dependencies

Open your terminal and run the following command.

npx create-next-app@latest --ts
Enter fullscreen mode Exit fullscreen mode

Although this example uses the Next.js app directory, the same approach also works with the Next.js pages directory.

Terminal showing our Next.js application configuration

Cd into your project directory and install Next-auth

npm install next-auth
Enter fullscreen mode Exit fullscreen mode

Configuring authentication providers

Create a file at this path /app/api/auth/[...nextauth]/route.ts. If you are using the pages directory you can follow this guide to configure your provider

import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";

// users DB
const usersDB = [
  {
    name: "John Done",
    id: "1",
    email: "john.doe@email.com",
    image: "",
  },
  {
    name: "Michael Jackson",
    id: "2",
    email: "michael.jackson@email.com",
    image: "",
  },
  {
    name: "Jack Hammer",
    id: "3",
    email: "jack.hammer@email.com",
    image: "",
  },
];

const handler = NextAuth({  
    // Here is where we place our providers

providers: [
    Credentials({
      id: "account-switch",
      name: "Account Switch",
      credentials: {
        id: {
          label: "id",
          placeholder: "Enter id",
          type: "text",
          value: "",
        },
        email: {
          label: "email",
          placeholder: "Enter email",
          type: "email",
        },
      },
      authorize(credentials, req) {
        // Validate user
        /**
         * You can make an aditional request to your backend
         * to verify this user i.e get a new access/refresh token etc.
         *
         */

        const user = usersDB.find((user) => {
          return user.email === credentials?.email;
        });

        if (!user) throw new Error("Account not found!");

        // Verify Token
        /**
         * Ideally this would be an api call to your backend to verify the token
         */
        const isTokenValid = true;

        if (!isTokenValid) throw new Error("Token Invalid! Log in again");

        return {
          name: user.name,
          id: user.id,
          email: user.email,
          image: user.image,
        };
      },
    }),
});

export { handler as GET, handler as POST }
Enter fullscreen mode Exit fullscreen mode

We use the credentials provider to implement a custom authentication strategy. For example, if we want users to log in using their username, phone number, and password, we can pass those values and implement our own authentication logic.

Set up accounts manager

The accounts manager is responsible for storing and managing all the accounts that users have used on your app. You can use any state management solution such as Redux, Zustand, Jotai, React Context etc.

In this example, we used React Context as the tool for managing our account state.

Create an account-manager.tsx file

import { User } from "next-auth";
import { ReactNode, createContext, useContext, useState } from "react";

const accountManager = createContext<{
  accounts: User[];
  addAccount: (account: User) => void;
}>({ accounts: [], addAccount: (v) => {} });

export default function AccountManagerProvider({
  children,
}: {
  children: ReactNode;
}) {
  const [accounts, setAccounts] = useState<User[]>([
    // Default accounts - just for testing
    {
      name: "John Done",
      id: "1",
      email: "john.doe@email.com",
      image: "",
    },
    {
      name: "Michael Jackson",
      id: "2",
      email: "michael.jackson@email.com",
      image: "",
    },
  ]);

  return (
    <accountManager.Provider
      value={{
        accounts,

                // this can be used when a user logs in
        addAccount: (account) => {
          setAccounts((prev) => [...prev, account]);
        },

      }}
    >
      {children}
    </accountManager.Provider>
  );
}

// useAccountManager Hook
export function useAccountManager() {
  return useContext(accountManager);
}
Enter fullscreen mode Exit fullscreen mode

Set up your root providers

Providers serve as the source of data or state that can be accessed by child components. In that vain we can access our session and our accounts

Create a projects.tsx file and input the following

"use client";

import { SessionProvider } from "next-auth/react";
import { ReactNode } from "react";
import AccountManagerProvider from "./accounts-manager";

export default function Providers({ children }: { children: ReactNode }) {
  return (
    <SessionProvider>
      <AccountManagerProvider>{children}</AccountManagerProvider>
    </SessionProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Wrap the children in your root layout.tsx file with the providers - this allows us access to the session data and user accounts state.

import "./globals.css";
import { Inter } from "next/font/google";
import Providers from "./providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Setup Switch Account Component

This component allows you to use to select an account and switch between them using the signIn method from next-auth/react .

Note: the select component is from ui.shadcn.com but any select component should be us effective.

"use client";

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "~/components/ui/select";
import { useAccountManager } from "./accounts-manager";
import { signIn, useSession } from "next-auth/react";
import Link from "next/link";
import type { Session } from "next-auth";

export function AccountSwitcher({ session }: { session?: Session | null }) {
****// You can pass the session in from a server component to avoid a rerender but you can use the useSession hook also
// const { data:session } = useSession();
  const { accounts } = useAccountManager();

  const currentAccount = accounts.find(
    (account) => account.name === session?.user?.name
  );

  return (
    <Select
      defaultValue={currentAccount?.id}
      onValueChange={async (value) => {
        const account = accounts.find((account) => account.id === value);
        if (!account) throw new Error("Account not found!");

        try {
          const result = await signIn("account-switch", { ...account });
        } catch (error) {
          console.log(error);
        }
      }}
    >
      <SelectTrigger className="w-[180px]" autoFocus>
        <SelectValue placeholder="Select an account" />
      </SelectTrigger>
      <SelectContent>
        {accounts.length < 1 ? (
          <p className="text-center">
            <Link href="/login" className="text-sm underline">
              Login
            </Link>
          </p>
        ) : (
          <>
            {accounts.map((account) => {
              return (
                <SelectItem key={account.id} value={account.id}>
                  {account.name}
                </SelectItem>
              );
            })}
          </>
        )}
      </SelectContent>
    </Select>
  );
}

Enter fullscreen mode Exit fullscreen mode

Update your page

Import the AccountSwitcher in any component.

import { getServerSession } from "next-auth";
import { AccountSwitcher } from "./account-switcher";

export default async function Home() {
  const session = await getServerSession();

  return (
    <main className="container">
      <div className="flex flex-col items-center justify-center min-h-screen">
        <div className="text-center mb-4">
          {session ? `Currently Signed in as ${session.user?.name}` : null}
        </div>
        <AccountSwitcher session={session} />
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Possible Improvements

  • To persist user accounts, you can save the data in a storage solution like LocalStorage/Redis.
  • Security - It might not be best to store your authorisation token in LocalStorage. Instead, you can store each user's token in a cookie with a unique identifier, such as _userId:token. To manage cookies in Next.js, you can use the cookie helpers provided by Next.js

Code Repository

Here is a link to the code repository.

💖 💪 🙅 🚩
oreoluwabs
Oreoluwa Bimbo-Salami

Posted on June 3, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related