In this chapter we're going to install NextAuth
and setup a basic example with Google provider. Note that our Next
app only has a single page, the home page (/app/index
) and a single component <Navbar />
that has one link to the home page. The finished code for this chapter is available on github (branch: basicgoogleprovider).
Install
Install NextAuth
in the frontend folder:
npm i next-auth
Next, we setup a route handler for NextAuth
. We create a route.ts
file in a new folder src/app/api/auth/[...nextauth]/route.ts
. The [...nextauth]
folder is a catch all route in Next
. This means that all requests to api/auth
or to for example api/auth/local/register
will be handled by our newly created route.ts
route handler. Put this inside the handler:
// frontend/src/app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import { authOptions } from './authOptions';
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Create the authOptions.ts
file that gets referred to above.
Note here: authOptions
is just an object with configuration and customizations properties. Most tutorials just write it as an object literal in the route handler. However, when running Next build
it gave me a TypeScript error at a certain point. One of the NextAuth
authors suggested to place the authOptions
in a separate file. This did solved the problem and that is why we are putting authOptions
in a separate file.
// frontend/src/app/api/auth/[...nextauth]/authOptions.ts
import { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID ?? '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '',
}),
],
};
What happens here: we add our first provider to the providers list. We then configure GoogleProvider
that we import from NextAuth
. The client id and secret come from the .env
file that we wrote in chapter 1.
By the way, to read the NextAuth
docs on this, just google NextAuth provider google
and that will lead you to the docs page where you will see this setup.
Finally, add following lines to your .env.local file: (This isn't strictly required in development but will prompt warnings in your terminal.)
# frontend/.env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=EDITME->somesecret
Where NEXTAUTH_SECRET
is a secret you need to generate. For windows user, open bash and enter following:
openssl rand -base64 32
Copy the return string and paste it into your .env.local
file. Other platforms I'm not sure, look it up.
Our first login with GoogleProvider in NextAuth
We are now ready for our first login. Yes, it is that easy. We create a login button:
// src/components/header/SignInButton.tsx
'use client';
import { signIn } from 'next-auth/react';
export default function SignInButton() {
return (
<button
type='button'
className='bg-sky-400 rounded-md px-4 py-2'
onClick={() => signIn()}
>
sign in
</button>
);
}
Firstly, we make it a client component because our button needs an event handler. In the onClick event we pass signIn
. This is a function from NextAuth
that when called will start the sign in procedure.
We only have our homepage right now but that is ok. In our current NextAuth
configuration there is no custom login page and NextAuth
will redirect us to a NextAuth
default login page. We will customize this later with our own page. Let's run our Next
frontend and click the sign in button. We are redirected to http://localhost:3000/api/auth/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F
(with callback parameter to home page):
This is the default login page NextAuth
provides and there are some things we need to say about this:
- Obviously, it not a nice design.
- You cannot change this page with f.e. other styling.
- If you're not coding along you won't know this but clicking the login button triggered a full page reload (F5 reload).
Conclusion, this is not something you would want to use in production but here, it does serve some purpose. Let's click the Sign in with Google link and see what happens:
- We are redirected to
https://accounts.google.com/o/oauth2/v2/auth/...
(only on first login) - We are asked what google account we want to use. (only on first login)
- We are asked if we want to login to -appname- and that means that Google will share following data -some data-. (only on first login) (These align with the settings we made in chapter 1, Google OAuth setup)
- On clicking continue we are redirected to our frontend homepage
localhost:3000
.
If we logout and login again (which we can't yet), the first 3 steps will be skipped. This is just the classic authentication flow with Google as I'm sure we've all done.
But, it works, so yay! What we're missing is feedback. Are we logged in or not? We are but we don't know and that is our next step.
NextAuth Session
We are now signed in using NextAuth
. Practically, that means that NextAuth
verified us with Google and then set some cookies, one with a JWT token
. But, we don't have to worry about cookies.
NextAuth
provides us with 2 ways to verify if a user is logged in:
- For client components: the
useSession
hook. - For server components: the
getServerSession
function.
NextAuth useSession hook
useSession
is a hook, so you can only use it in client components. It's just a hook, so you call it (no parameter required) and it returns an object that you destructure.
'use client';
import { useSession } from 'next-auth/react';
const { data: session, status, update } = useSession();
For now we ignore status and update and focus on the data property that we rename to session. Our session will be either null/undefined (not logged in) or an object of type DefaultSession:
{
user?: {
name?: string | null
email?: string | null
image?: string | null
}
expires?: string
}
This session interface can and will be customized, meaning we put more data on it.
NextAuth getServerSession() function
getServerSession
is an async function that you use in server components or route handlers.
- It takes one parameter, the
authOptions
object that we pass in ourapp/api/auth/[...nextAuth]/route.ts
route handler. - It returns either null (not logged in) or the default session from just above.
- Notice the async and await keywords!
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/authOptions';
export default async function MyComponent() {
const session = await getServerSession(authOptions);
// ...
}
Let's add this to our code to get a better feel for it. Firstly though, to run useSession
, we have to wrap our entire app into a provider that NextAuth
gives us.
NextAuth SessionProvider
Things get a bit complicated here but in the end it is just a configuration so don't worry about it too much.
The problem is that the SessionProvider
that NextAuth
gives us is a client component because it uses React
hooks. But, since NextAuth v4
is getting a bit older, it doesn't use the use client
directive. This makes Next
throw an error.
You can read more about this problem and how to solve it in the Next docs. The solution is to import SessionProvider
in a client component and immediately export it like this:
// frontend/src/app/component/Provider.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export default SessionProvider;
We now can use this <Provider />
component to wrap around our app inside our root layout file:
// frontend/src/app/layout.tsx
//...
import NavBar from '@/components/header/Navbar';
import { SessionProvider } from 'next-auth/react';
import { getServerSession } from 'next-auth';
import { authOptions } from './api/auth/[...nextauth]/authOptions';
//...
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(authOptions);
return (
<html lang='en'>
<body className={`${inter.className} px-2 bg-zinc-200`}>
<SessionProvider session={session}>
<div className='max-w-6xl mx-auto'>
<NavBar />
<main className='my-4'>{children}</main>
</div>
</SessionProvider>
</body>
</html>
);
}
Note how we made RootLayout async but again, don't worry to much about this, in the end it is just configuration. We continue in the next chapter.
If you want to support my writing, you can donate with paypal.