Dipesh Wagle's Logo
BlogLibrary

Use custom backend with next-auth

Use next-auth to authenticate users using existing custom backend

Author Image

Written By

Dipesh Wagle

Published on December 1, 2021

Use custom backend on next-auth

I've been using next-auth for some projects and it is really great and has an easy way to add different providers. But I recently had a project that required me to add auth to Next.js project, obviously I wanted to use next-auth, but the only challenge was that it had a custom backend that also handled auth. It took some time for me to figure out the workflow for using custom backend using next-auth. So in this post, I am going to show the process for adding a custom backend that uses jwt and also requires refreshing token.

Let's assume that we have an endpoint for login /api/auth and /api/auth/refresh-token. First we are going to install next-auth package and create a [...nextauth].js. Since we are using a custom backend we will use Credentials Provider.

Credentials Provider intended to support use cases where you have an existing system you need to authenticate users against. You can read more about it here https://next-auth.js.org/providers/credentials

Setup Provider

The First thing we must do is setup a Provider. We are going to call the custom backend with the user's credentials in the authorize handler. We only check if there is data in the response for now but we can add more checks if needed.

const providers = [
Providers.Credentials({
name: 'Credentials',
authorize: async (credentials) => {
const response = await axios.post('/api/auth', {
email: credentials.email,
password: credentials.password,
});
if (response.data) {
return response.data,
}
return null;
},
}),
];

Callbacks and Refresh Token

Then we are going to setup the callbacks for jwt and session. Here you can see we are also checking if the token is expired and if it is expired we are going to refresh the token. We are going to use the jwt callback to if the token is valid by comparing the accessTokenExpires. If it is valid we persist it in the session otherwise we are going to refresh the token.

If you want to pass data such as an Access Token or User ID to the browser when using JSON Web Tokens, you can persist the data in the token when the jwt callback is called, then pass the data through to the browser in the session callback. You can read more about it here https://next-auth.js.org/providers/credentials

const callbacks = {
async jwt(prevToken, token) {
if (token) {
return {
accessToken: token.data.accessToken,
refreshToken: token.data.refreshToken,
accessTokenExpires: token.data.accessTokenExpires,
};
}
if (Date.now() < prevToken.accessTokenExpires) {
return prevToken;
}
return refreshAccessToken(prevToken);
},
async session(session, token) {
return {
...session,
refreshToken: token.refreshToken,
accessToken: token.accessToken,
};
},
};

The function to refresh the token looks like this, we basically call the refreshToken endpoint and get the new tokens.

const refreshAccessToken = async ({ refreshToken }) => {
try {
const response = await axios.post("/api/auth/refresh-token", {
refreshToken,
});
if (response.data) {
return { ...response.data, refreshToken };
}
} catch (error) {
return {
error: "AuthError",
};
}
return null;
};

Conclusion

This is how the [...nextauth].js file looks after setting everything up.

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from 'axios'
const refreshAccessToken = async ({ refreshToken }) => {
try {
const response = await axios.post("/auth/refresh-token", {
refreshToken,
});
if (response.data) {
return { ...response.data, refreshToken };
}
} catch (error) {
return {
error: "AuthError",
};
}
return null;
};
const providers = [
Providers.Credentials({
name: 'Credentials',
authorize: async (credentials) => {
const response = await axios.post('/auth', {
email: credentials.email,
password: credentials.password,
});
if (response.data) {
return response.data,
}
return null;
},
}),
];
const callbacks = {
async jwt(prevToken, token) {
if (token) {
return {
accessToken: token.data.accessToken,
refreshToken: token.data.refreshToken,
accessTokenExpires: token.data.accessTokenExpires,
};
}
if (Date.now() < prevToken.accessTokenExpires) {
return prevToken;
}
return refreshAccessToken(prevToken);
},
async session(session, token) {
return {
...session,
refreshToken: token.refreshToken,
accessToken: token.accessToken,
};
},
};
const options = {
providers,
callbacks,
};
export default (req, res) => NextAuth(req, res, options);

By using the Credentials Provider in next-auth we can use a custom backend for authentication. We can also use the refresh token if it has expired by using callbacks.

References

Using Credentials provider with a custom backend in NextAuth.js!

Next.js authentication with existing backend

2022 - dw