Hey Devs !
As a front-end developer my nightmare was to handle the user authentication.
With NextAuth.js (I will call it auth.js since they are changing their name) the user authentication handling is much easier. Let's take a look at how we handle it with Next.js
Here are the steps that you need to follow;
Create a next app.
yarn create next-app
Go inside your project and add auth.js
yarn add next-auth
To implement the auth.js, we need to create an api folder in our pages directory and create the [...nextauth].js
/pages
/api
/auth
[...nextauth].js
In your pages/api/auth/[...nextauth].js
create the default function and credential provider.
Since you can configure your NextAuth, we will create an authOptions object to store the configurations that we want aplly.
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {}
export default NextAuth(authOptions);
Now let's start to adding options
Since we are using custom backend, we are going to use CredentialsProvider and we will pass this to providers key. This key stores all of providers that you want to use in your application with their configurations.
providers: [
CredentialsProvider({
type: "credentials",
credentials: {
email: {
label: "Email",
type: "email",
},
password: { label: "Password", type: "password" },
},
],
...rest
If you want to use your own Sign In page, you do not need to pass email and password keys to credentials object. Anyway, we are going to use Auth.js's sign in page in this.
In this case we need the email address and password of user to authenticate so we pass email and password fields.
Now we all set in [...nextAuth].js
file with its credentials object.
to fetch login data to your custom backend now we need to pass authorize
function with your custom sign in api;
async authorize(credentials) {
const credentialDetails = {
email: credentials.email,
password: credentials.password,
};
const resp = await fetch(backendURL + "/auth/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(credentialDetails),
});
const user = await resp.json();
if (user.is_success) {
return user;
} else {
console.log("check your credentials");
return null;
}
},
In this case, backendURL
is a constant of your server's ip and host, and the auth/login
is the endpoint of your backend.
If your credentials are correct, the backend should response you a success message and you need to check it if its success or not.
For now the [...nextAuth].js
is something like this:
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
const backendURL = process.env.NEXT_PUBLIC_BACKEND_URL;
export const authOptions = {
providers: [
CredentialsProvider({
type: "credentials",
credentials: {
email: {
label: "Email",
type: "email",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const credentialDetails = {
email: credentials.email,
password: credentials.password,
};
const resp = await fetch(backendURL + "/auth/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(credentialDetails),
});
const user = await resp.json();
if (user.is_success) {
console.log("nextauth daki user: " + user.is_success);
return user;
} else {
console.log("check your credentials");
return null;
}
},
}),
],
};
export default NextAuth(authOptions);
since we are using JWT , you need to pass session object with strategy key as:
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
you can pass a maxAge to store your user in the web browser as authenticated as you want, here it stores for 30 days.
Do not forget that JWT maxAge can be also defined in the backend so you should consider the backend JWT maxAge when
setting this value.
Implementing the callbacks to use session in client-side
Now you can fetch user in backend and your response should have some information that will be used in the client side while fetching some APIs.
With the help of callbacks, we will persist the backend access token to token provided by auth.js right after signin.
For this, we need to pass callbacks object to [...nextauth].js
file as:
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.email = user.data.auth.email;
token.username = user.data.auth.userName;
token.userType = user.data.auth.userType;
token.accessToken = user.data.auth.token;
}
return token;
},
}
In this case, from the response provided by backend, user object has various values like email,userName,userType and token.
In jwt object we are calling the async function that stores our response in token.
After that, from frontend, we need to add the token to cookies in user session, like this :
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.email = user.data.auth.email;
token.username = user.data.auth.userName;
token.user_type = user.data.auth.userType;
token.accessToken = user.data.auth.token;
}
return token;
},
session: ({ session, token, user }) => {
if (token) {
session.user.email = token.email;
session.user.username = token.userName;
session.user.accessToken = token.accessToken;
}
return session;
},
},
with the help of session callback, now we can use the useSession hook provided by auth.js in the client-side to get information that we pass to session object.
Before going into the client side, lets check your [...nextAuth].js
:
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
const backendURL = process.env.NEXT_PUBLIC_BACKEND_URL;
export const authOptions = {
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
providers: [
CredentialsProvider({
type: "credentials",
credentials: {
email: {
label: "Email",
type: "email",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const credentialDetails = {
email: credentials.email,
password: credentials.password,
};
const resp = await fetch(backendURL + "/auth/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(credentialDetails),
});
const user = await resp.json();
if (user.is_success) {
console.log("nextauth daki user: " + user.is_success);
return user;
} else {
console.log("check your credentials");
return null;
}
},
}),
],
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.email = user.data.auth.email;
token.username = user.data.auth.userName;
token.user_type = user.data.auth.userType;
token.accessToken = user.data.auth.token;
}
return token;
},
session: ({ session, token, user }) => {
if (token) {
session.user.email = token.email;
session.user.username = token.userName;
session.user.accessToken = token.accessToken;
}
return session;
},
},
};
export default NextAuth(authOptions);
Use the session in client side !
Lets start wrapping our application with SessionProvider;
Move to your _app.js file in the route;
/pages
_app.js
and import SessionProvider from next-auth
import { SessionProvider } from "next-auth/react";
As well as you import the session provider, do not forget to pass session in to the page props and wrap your application with SessionProvider like this in your _app.js
file;
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
After that everything is simple, just import useSession()
hook from next-auth and handle any session information that you passed in the session callback function in [...nextauth].js
file.
import { useSession} from "next-auth/react";
const { data, status } = useSession();
Now you can use any data that you passed in session callback function by using data
object from useSession().
In this example we can access the user access token which we got from server as : session.user.accessToken
Also, you can check if the user is authenticated in the serverside, so you may want to protect your pages.
In your component, you may use session in getServerSideProps function as:
import getSession to use session in serverside from :
import {getSession } from "next-auth/react";
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
//if not exists, return a temporary 302 and replace the url with the given in Location.
context.res.writeHead(302, { Location: "/signin" });
context.res.end();
//do not return any session.
return { props: {} };
}
}
And this is a simple use of credentials provider from Auth.js
Do let me know if you have any suggestions or questions in the commends !
Top comments (10)
Quite useful content and very clear explanation, thanks @ekimcem
Thank you, @ekimcem.
One of the best articles on this topic.
what does the expected fetch response look like? and what does should return after fetch? my response look like this
Actually it depends what you want to return when the user signin.
Generally I return the user data like username, email, user type, user id, etc.
When the user signs in you will need some of the user's properties in order to display relevant data in your app.
so it depends on your app and backend structure. @haniframadhani
I have my custom signin page, How can I adjust your code in my code ?
@sait
For custom signin page, you should add "pages"object into your nextauth config;
like in the example
and then assign your custom signin page route into the signIn.
The thing is you need to import signin function from nextauth and provide the required credentials into that function.
Thanks
Can you make an updated version of this code in TS?
Will add it soon.