Building an Account switcher with NextJS and Next Auth
Tags: webdev, react, tutorial, javascript
Published: 2023-06-03 04:05:39

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/been5ye16zf0jd70pho4.png)




## 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. 

```markdown
npx create-next-app@latest --ts
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hita17sxzo5efn7ko73w.png)


Cd into your project directory and install Next-auth

```markdown
npm install next-auth
```

## 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](https://next-auth.js.org/configuration/initialization#api-routes-pages) to configure your provider

```tsx
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 }
```

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](https://redux-toolkit.js.org/), [Zustand](https://zustand-demo.pmnd.rs/), [Jotai](https://jotai.org/), [React Context](https://react.dev/learn/passing-data-deeply-with-context) etc.

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

Create an `account-manager.tsx` file

```tsx
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);
}
```

## 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

```tsx
"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>
  );
}
```

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

```tsx
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>
  );
}
```

## 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](http://ui.shadcn.com) but any select component should be us effective.

```tsx
"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>
  );
}

```

## Update your page

Import the AccountSwitcher in any component.

```tsx
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>
  );
}
```

## 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](https://nextjs.org/docs/app/api-reference/functions/cookies)


## Code Repository
Here is a link to the [code repository](https://github.com/oreoluwa-bs/account-switcher-next-js).