reactjsauthenticationnext.jsaxiosnext-auth

Update Next.js to React 18 breaks my API calls using next-auth


This is a strange one, but here's the situation.

I'm using Next.js with the Next-auth package to handle authentication.

I'm not using Server-Side rendering, it's an admin area, so there is no need for SSR, and in order to authenticate users, I've created a HOC to wrap basically all components except for the "/sign-in" route.

This HOC all does is check if there's a session and then adds the "access token" to the Axios instance in order to use it for all async calls, and if there is no session, it redirects the user to the "sign-in" page like this ...

const AllowAuthenticated = (Component: any) => {
  const AuthenticatedComponent = () => {
    const { data: session, status }: any = useSession();
    const router = useRouter();

    useEffect(() => {
      if (status !== "loading" && status === "unauthenticated") {
        axiosInstance.defaults.headers.common["Authorization"] = null;
        signOut({ redirect: false });
        router.push("/signin");
      } else if (session) {
        axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${session.accessToken.accessToken}`;
      }
    }, [session, status]);

    if (status === "loading" || status === "unauthenticated") {
      return <LoadingSpinner />;
    } else {
      return <Component />;
    }
  };

  return AuthenticatedComponent;
};

export default AllowAuthenticated;

And in the Axios instance, I'm checking if the response is "401", then I log out the user and send him to the "sign-in" screen, like this ...

axiosInstance.interceptors.response.use(
  response => response,
  error => {
    const { status } = error.response;
    if (status === 401) {
      axiosInstance.defaults.headers.common["Authorization"] = null;
      signOut({ redirect: false });
      return Promise.reject(error);
    }
    return Promise.reject(error);
  },
);

Very simple stuff, and it works like a charm until I decided to upgrade my project to use "react 18.1.0" and "react-dom 18.1.0", then all of a sudden, my API calls doesn't get the "Authorization" header and they return "401" and the user gets logged out :(

If I tried to make an API call inside the HOC right after I set the Auth headers it works, sot I DO get the "token" from the session, but all the async dispatch calls inside the wrapped component return 401.

I forgot to mention, that this issue happens on page refresh, if I didn't refresh the page after I sign in, everything works great, but once I refresh the page the inner async dispatch calls return 401.

I Updated all the packages in my project including Axios and next-auth, but it didn't help.

I eventually had to downgrade back to "react 17.0.2" and everything works again.

Any help is much appreciated.


Solution

  • For those of you who might come across the same issue.

    I managed to solve this by not including the logic for adding the token to the "Authorization" header inside the HOC, instead, I used a solution by @kamal-choudhary on a post on Github talking about how to add "JWT" to every axios call using next-auth.

    Using @jaketoolson help at that Github post, he was able to attach the token to every "Axios" call.

    The solution is basically to create an Axios instance and add an interceptor like I was doing above, but not just for the response, but also for request.

    You'll add an interceptor for every request and check if there's a session, and then attach the JWT to the Authorization header.

    That managed to solve my issue, and now next-auth works nicely with react 18.

    Here's the code he's using ...

    import axios from 'axios';
    import { getSession } from 'next-auth/react';
    
    const baseURL = process.env.SOME_API_URL || 'http://localhost:1337';
    
    const ApiClient = () => {
      const defaultOptions = {
        baseURL,
      };
    
      const instance = axios.create(defaultOptions);
    
      instance.interceptors.request.use(async (request) => {
        const session = await getSession();
        if (session) {
          request.headers.Authorization = `Bearer ${session.jwt}`;
        }
        return request;
      });
    
      instance.interceptors.response.use(
        (response) => {
          return response;
        },
        (error) => {
          console.log(`error`, error);
        },
      );
    
      return instance;
    };
    
    export default ApiClient();
    

    https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1993281

    https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1898233