Article cover image

Sharing authentication between Next.js projects with NextAuth

  • Chalo
    Chalo
    Senior Engineer

There are lots of large systems divided into smaller modules and it is very common that some of these modules need to share the same authentication status. In this article we're going to explain how two independent Next.js projects can share the same authentication status using NextAuth.js.

If you are in a hurry and want to go straight to the code, here is the repo.

Now, let's suppose that you have a company website, mainly for marketing purposes but then the company wants to add a module where the users have a more personalized experience, for this the users will need to create an account and then sign in to the site but the session status should be visible also from the website.

Let's start with two Next.js projects, one for the website and one for the user portal. The website is going to be our main project, that is, it will be on our main domain. i.e. example.com. The user portal will live in a subdirectory of the main domain like example.com/portal. This is important because we are basing this on what Vercel calls Next.js Multi Zones.

Running both projects in different ports

Before we start, I recommend to modify your package.json files in the projects to be able to run both projects at the same time but in different ports. In this case we're just going to modify the port in which the website will run, so in the scripts section we change the dev script to:

...

"scripts": {
    "dev": "next dev -p 4000",
    ...
}

...

Portal project configuration

We'll start setting up the portal project. This is the project that will provide user specific features, so here's where users will need to create an account and log in. Therefore, here's where we will manage user's sessions with NextAuth.

The first step is to add the basePath so the root of the project will be /portal instead of /. This is done in the next.config.js file.

// portal/next.config.js

module.exports = {
  // ...

  basePath: "/portal",

  // ...
};

👉 Remember to restart your server after making changes to the next.config.js file

Now, if we run the project we can confirm that the basePath is being applied. If we go to http://localhost:3000/ we will get a 404 error but if we go to http://localhost:3000/portal our site will be running there.

NOTE: The portal project is running in the default port which is 3000.

The next step is to add NextAuth to our project and set it up according to the docs:

1. Install NextAuth

2. Add the environment variables

# portal/.env.local

GITHUB_ID=[COPY VALUE FROM YOUR GITHUB OAUTH APP]
GITHUB_SECRET=[COPY VALUE FROM YOUR GITHUB OAUTH APP]
NEXTAUTH_URL=http://localhost:4000/portal/api/auth
NEXT_PUBLIC_PORTAL_BASE_PATH=/portal

If you want to use Github Authentication, you should add a new OAuth application in your account and copy the values for GITHUB_ID and GITHUB_SECRET. If you want to add any other auth provider with NextAuth see the docs.

Notice that we're setting the NEXTAUTH_URL to point to http://localhost:4000 which is the URL of the website. This is because we want the website to be the main entry point of our site. If it does not makes sense to you, just be patient, this will make sense when we start configuring the Next.js rewrites.

3. Add the NextAuth API Route

// portal/src/pages/api/auth/[...nextauth].js

import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
export default NextAuth({
  // Configure one or more authentication providers
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    // ...add more providers here
  ],
});

4. Add the NextAuth SessionProvider context

// portal/src/pages/_app.js

import { SessionProvider } from "next-auth/react";

function App({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider
      session={session}
      basePath={`${process.env.NEXT_PUBLIC_PORTAL_BASE_PATH}/api/auth`}
    >
      <Component {...pageProps} />
    </SessionProvider>
  );
}

export default App;

5. Frontend - Add React Hook

We can use the useSession React hook from NextAuth to create a LoginButton component which will show us a button to start the Github authentication process. After the user has signed in this component will show us the user information and will show a sign out button too.

// portal/src/components/LoginButton.jsx

import { useSession, signIn, signOut } from "next-auth/react";

export default function LoginButton() {
  const { data: session } = useSession();

  if (session) {
    return (
      <>
        Signed in as {session.user.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }

  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn("github")}>Sign in with Github</button>
    </>
  );
}

6. Add the button to the HomePage

We're keeping things simple here so our portal home page will show just a message and the LoginButton component.

// portal/src/pages/index.js

import LoginButton from "../components/LoginButton";

const HomePage = () => (
  <>
    <h1>Welcome to the user portal home page</h1>
    <LoginButton />
  </>
);

export default HomePage;

Ok, we have just set up the portal site to manage the user authentication 🎉

But we want to show the session state in the website too, and here is where the interesting part of this article comes. Ready?

Website project configuration

In order to the website to be aware of the session status, we need to add NextAuth to this project too, similar to what we just did for the portal site, but with a few changes. Let's see.

NOTE: We're not adding the NextAuth API route here, because we will use the one we just added to the portal site.

1. Install NextAuth

2. Add the environment variables

# website/.env.local
NEXTAUTH_URL=http://localhost:4000/portal/api/auth
NEXT_PUBLIC_PORTAL_URL=http://localhost:3000
NEXT_PUBLIC_PORTAL_BASE_PATH=/portal

3. Add the NextAuth SessionProvider context

// website/src/pages/_app.js

import { SessionProvider } from "next-auth/react";

function App({ Component, pageProps }) {
  return (
    <SessionProvider
      session={pageProps.session}
      // 👇 Notice that the basePath will be <WEBSITE_URL>/portal/api/auth
      basePath={`${process.env.NEXT_PUBLIC_PORTAL_BASE_PATH}/api/auth`}
    >
      <Component {...pageProps} />
    </SessionProvider>
  );
}

export default App;

Notice that NextAuth will use <WEBSITE_URL>/portal/api/auth as the basePath for all the authentication routes. As we said before, we want the website to be the main entry point to our site. If it seems confusing, again, this will make sense in a little when we configure the rewrites.

4. Configure the rewrites in Next.js for the website

The rewrites for Next.js are configured in the next.config.js file. You just need to add the following:

// website/next.config.js

module.exports = {

	// ...

  async rewrites() {
    return {
      beforeFiles: [
        // Anything pointing to <WEBSITE_URL>/portal/* will be rewritten to <PORTAL_URL>/portal/*
        // including <WEBSITE_URL>/portal => <PORTAL_URL>/portal
        {
          source: `${process.env.NEXT_PUBLIC_PORTAL_BASE_PATH}/:path*`,
          destination: `${process.env.NEXT_PUBLIC_PORTAL_URL}${process.env.NEXT_PUBLIC_PORTAL_BASE_PATH}/:path*`,
        },
      ],
    };
  },

	// ...

};

👉 Remember to restart your server after making changes to the next.config.js file.

What we are telling to Next.js here is that any URL that has /portal should point to the respective route in the user portal site.

5. Frontend - Add React Hook

As we did in the portal project, we are going to add a LoginButton component using the useSession hook from NextAuth.

// portal/src/components/LoginButton.jsx

import { useSession, signIn, signOut } from "next-auth/react";

export default function LoginButton() {
  const { data: session } = useSession();

  if (session) {
    return (
      <>
        Signed in as {session.user.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }

  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn("github")}>Sign in with Github</button>
    </>
  );
}

I know, you might be saying "It is a bad practice to duplicate code” but I want to keep things simple here. There will be another version of this article but using Turborepo and we will set up everything to be able to share components between both projects.

6. Add the button to the HomePage

We're keeping things simple here so our portal home page will show just a message and the LoginButton component.

// portal/src/pages/index.js

import LoginButton from "../components/LoginButton";

const HomePage = () => (
  <>
    <h1>Welcome to the user portal home page</h1>
    <LoginButton />
  </>
);

export default HomePage;

Adding rewrites for other pages

If you want to add more routes for other pages you may have in the portal site, you just need to add more rewrites here, for example, we will add a new page /my-results to the portal site with the following code:

// portal/pages/my-results.js

const MyResultsPage = () => {
  return <div>This is were the user results will be listed.</div>;
};

export default MyResultsPage;

We can allow our users to go to <WEBSITE_URL>/my-results instead of <WEBSITE_URL>/portal/my-results by adding another rewrite:

// website/next.config.js

// ... other rewrites
{
  source: '/my-results',
  destination: `${process.env.NEXT_PUBLIC_PORTAL_URL}${process.env.NEXT_PUBLIC_LOGIN_BASE_PATH}/my-results`
},

👉 Remember to restart your server after making changes to the next.config.js file.

We can use the same for all other pages we have in our portal site.

Fixing NextAuth wrong redirects

There's one problem related to the NextAuth redirects. If an unauthenticated user tries to go to a protected page, NextAuth will redirect the user to <WEBSITE_URL>/api/auth/… this will result in a 404 error because there's no NextAuth API Route in the website project only in the portal project.

Let's add protection to our /my-results route using the useSession hook from NextAuth:

// portal/pages/my-results.js

import { useSession } from "next-auth/react";

const MyResultsPage = () => {
  // The { required: true } tells NextAuth that 
  // this page is only accesible to authenticated users
  // unauthenticated users will be redirected
  const { status } = useSession({ required: true });

  if (status === "loading") {
    return <p>Loading...</p>;
  }

  return <div>This is were the user results will be listed.</div>;
};

export default MyResultsPage;

Now, if we sign out of the site and try to go to http://localhost:4000/my-results or http://localhost:4000/portal/my-results we will be redirected to a 404 page.

We can fix this by adding one more rewrite to the website. Anything pointing to <WEBSITE_URL>/api/auth/* will be rewritten to <PORTAL_URL>/portal/api/auth/* because we want all the auth routes to be managed in the portal site.

// website/next.config.js

// ... other rewrites
{
  source: `/api/auth/:path*`,
  destination: `${process.env.NEXT_PUBLIC_PORTAL_URL}${process.env.NEXT_PUBLIC_PORTAL_BASE_PATH}/api/auth/:path*`,
},

👉 Remember to restart your server after making changes to the next.config.js file.

Done 🥳

This a simple version of what can be done with NextAuth and some Next.js features. The caveat in this version is that we have to duplicate some code i.e. for the LoginButton component, but we can also use Turborepo in order to allow component sharing between projects (coming soon).