(Part 2) How to configure social authentication in a Next.js + Next-Auth + Django Rest Framework application

with automated refreshing of access tokens and discussing the pitfalls of useSession() hook (as of Apr 30, 2021)

Mahieyin Rahmun
5 min readApr 30, 2021

This is the second part of a two-part series where I show you a way to connect your Django Rest Framework backend with a Next.js + Next-Auth app. If you are looking for the first part in the series where I lay out the basic concepts and write the first part of the code that only deals with obtaining access tokens from the DRF backend to use in the Next.js + Next-Auth client, head over here.

Disclaimer: The pitfalls of the useSession() hook of the next-auth package mentioned in this article is valid at the time of writing, but may not be the case depending on when you are reading this. The solution to the pitfall is currently being actively worked on.

In my last article, I talked about the basic template of configuring a Next.js + NextAuth app with Django Rest Framework. It works, but it has some flaws:

  • No way to use the refresh token
  • useSession() happens to have its own set of pitfalls (more on that later)

Even though dj-rest-auth package gives us endpoints to obtain rotating access and refresh tokens, we are not making use of those endpoints. Let’s fix that.

Refreshing access token

In order to refresh the access token, we need to understand how the Next-Auth flow works. You can find the details in the Next-Auth documentation on callbacks, but basically:

  • User logs in. The code in [...nextauth].ts is going to run.
  • The first callback to get immediately called after sign in is the signIn() callback.
  • After that, the jwt() and session() callbacks are called in succession.
  • Each time you use useSession() or getSession() , the jwt() and session() callbacks are run again.

As such, the refreshing mechanism needs to be done in the jwt() callback, and any modifications to be done to the session object is done in the session() callback. So, let’s do some modifications to the pages/api/auth/[...nextauth].ts file. Before that, create a new file client/constants/Utils.ts :

client/constants/Utils.ts

This file contains two functions: one to check for JWT token expiration, and the other for creating API endpoints. We depend on the jsonwebtoken package, which you can install by running yarn add jsonwebtoken and yarn add -D @types/jsonwebtoken if you need the type definitions. We will be using these two functions in the client/pages/api/auth/[...nextauth].ts file:

client/pages/api/auth/[…nextauth].ts

If you are here from Part 1 of my article, you would notice that the signIn() function is missing, and the associated logic is now inside jwt() callback. This was done after consultation with one of the maintainers of Next-Auth. You can read the conversation here, but basically, monkey-patching the user object the way I was doing it before is not actually needed. The rest of the code is self-explanatory: we check the expiration time of the access token, if it is nearly expired (refer to the Utils.ts file, we are offsetting current time by 60 seconds), then try to refresh it by communicating with the DRF backend. Also notice that we have refactored the backend base API endpoint into an environment variable.

Right now, with the current modifications in the [...nextauth].ts file, you have the ability to refresh the access tokens from the DRF backend, as long as the refresh token itself is valid.

All looks good, right? Well… not quite.

Pitfalls of the useSession() hook

Remember that in our client/pages/index.tsx file, we have the following code with the useSession() hook:

Old code in client/pages/index.tsx that uses the useSession() hook

It turns out that currently there is an issue with the useSession() hook as well as the getSession() function, which prevents them from automatically getting the updated sessionobject after the access token is refreshed. In other words, unless you do a hard page refresh, you won’t get the updated access token in your client and your subsequent API requests to the DRF backend that require a valid token will fail.

If you are looking for an example, I actually have one set up in the associated Github repository. For the brevity of this article, I am not going to write out everything, but the gist of it is:

  • There is a posts app in the DRF backend. The only view in the views.py file pings JSONPlaceholder and returns the posts it can find. This route requires authentication, e.g. the access token.
  • At the frontend, I am polling the above API endpoint at regular intervals of 10 seconds. Once the access token expires, even though it is being refreshed in the jwt() callback, the client still has the old access token. Therefore, the subsequent calls to the API will return 401 Unauthorized .

So, then, what’s the solution?

Custom useAuth() hook with useSwr()

To solve/work around the above problem, an approach that I came up with following this comment is to use the useSwr() hook in the swr package. Install the package first by running yarn add swr . I created a custom hook that will ping the session REST API provided by Next-Auth at regular intervals to keep the session object updated.

client/constants/Hooks.tsx

Side note about the approach above: I was actually reluctant to use this approach and instead wanted to get it done using built-in React hooks, namelyuseState() and useEffect() . However, I failed to do so, as you will see in this file in the Git repository. For those of you who are interested to know, the issue with using built-in hooks was that the page would arbitrarily refresh itself after the setInterval() callback fired, which results in absolutely crap user experience. Hence, I had to settle for a solution with the useSwr() hook. If any of you are able to solve the issue using built-in hooks, feel free to make a pull request in the Git repository.

So, now you can replace all calls to useSession() with useAuth() .

- const [session, loading] = useSession();
+ const { session, loading } = useAuth(); // default 20 seconds refresh interval for session
// OR, if you want to have custom refresh interval
+ const { session, loading } = useAuth(3 * 60); // 3 minutes refresh interval for session

Higher Order Component to get rid of code repetition (optional)

The last piece of the puzzle came in the form of a HOC, since I did not want to write the following lines in every single page of the app:

if (typeof window !== 'undefined' && loading) {
return null;
}
if (!loading && !session) {
return <AccessDenied />
}
return <MyComponent />

As such, I created a withAuth HOC.

Now, you can export your components by wrapping it with this HOC, and it will take care of handling the session object and showing an Access Denied component if the user is not logged in.

// default session refresh interval of 20 seconds
export default withAuth()(Posts);
// session refresh interval of 3 minutes
export default withAuth(3 * 60)(Posts);

Conclusion

This took way longer than I anticipated, but I learnt a lot in the process. I hope this series of articles was able to help you out in some ways to connect your Django backend with a Next.js + Next-Auth app. In hindsight, the same principles should apply for connecting basically any custom backend with Next-Auth and Next.js.

Feel free to leave your comments/criticisms/suggestions. The associated Github repository for all the code can be found here.

--

--

Mahieyin Rahmun

Hi, I am an aspiring Computer Science graduate from Bangladesh who takes interest in Web Development, ML and Automation.