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.
👉 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:
# 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
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.
// portal/src/pages/api/auth/[...nextauth].jsimportNextAuthfrom"next-auth";
importGithubProviderfrom"next-auth/providers/github";
exportdefaultNextAuth({
// Configure one or more authentication providersproviders: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// ...add more providers here
],
});
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.jsximport { useSession, signIn, signOut } from"next-auth/react";
exportdefaultfunctionLoginButton() {
const { data: session } = useSession();
if (session) {
return (
<>
Signed in as {session.user.email} <br /><buttononClick={() => signOut()}>Sign out</button></>
);
}
return (
<>
Not signed in <br /><buttononClick={() => 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.jsimportLoginButtonfrom"../components/LoginButton";
constHomePage = () => (
<><h1>Welcome to the user portal home page</h1><LoginButton /></>
);
exportdefaultHomePage;
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.
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.
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.jsximport { useSession, signIn, signOut } from"next-auth/react";
exportdefaultfunctionLoginButton() {
const { data: session } = useSession();
if (session) {
return (
<>
Signed in as {session.user.email} <br /><buttononClick={() => signOut()}>Sign out</button></>
);
}
return (
<>
Not signed in <br /><buttononClick={() => 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.jsimportLoginButtonfrom"../components/LoginButton";
constHomePage = () => (
<><h1>Welcome to the user portal home page</h1><LoginButton /></>
);
exportdefaultHomePage;
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.jsconstMyResultsPage = () => {
return<div>This is were the user results will be listed.</div>;
};
exportdefaultMyResultsPage;
We can allow our users to go to <WEBSITE_URL>/my-results instead of <WEBSITE_URL>/portal/my-results by adding another rewrite:
👉 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.jsimport { useSession } from"next-auth/react";
constMyResultsPage = () => {
// The { required: true } tells NextAuth that // this page is only accesible to authenticated users// unauthenticated users will be redirectedconst { status } = useSession({ required: true });
if (status === "loading") {
return<p>Loading...</p>;
}
return<div>This is were the user results will be listed.</div>;
};
exportdefaultMyResultsPage;
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.
👉 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).