Considering that we have:
Users should:
/login without flickering/profile without flickering)My logic right now for dealing with JWT:
// lib/axios.js
import Axios from 'axios';
import { getCookie, removeCookies } from 'cookies-next';
import qs from 'qs';
export const axios = Axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
paramsSerializer: (params) =>
qs.stringify(params, { arrayFormat: 'brackets' }),
withCredentials: true,
});
axios.interceptors.request.use(
(config) => {
const token = getCookie('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
config.headers.Accept = 'application/json';
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response.status === 401) {
// we need to implement refresh pattern
// const refresh = await refreshToken();
removeCookies('access_token');
}
return Promise.reject(error);
}
);
// For SWR
export const fetcher = async (...args) => await axios.get(...args).data;
I've been accumulating researches about this and I found so many different answers. So far I found:
_app.js with hard-coded private routes in an arraywithPrivate or withPublicgetServerSideProps with redirection to login inside every pagenextAuth but I'm not sure because it seems like it's building a backend and we've got one already_middleware that can do the redirection apparentlySWR, Suspense and Error Boundaries but I'm not sure if it's adapted for this kind of cases...Any clue about how I should do ?
If you don't use static generation and never plan to getServerSideProps works.
If you plan to use Vercel to host, _middleware could be a good option too, however, 3rd party hosting support is lagging.
Since you already have your own auth, nextAuth doesn't seem to be a good choice.
I'd recommend using an Auth context - wrapping every page with an HOC is isn't necessary anymore.
You can prevent the flickering by using a loading screen or spinner.
Here is an example of an auth context.
import { useEffect, useState, useCallback, createContext } from "react";
import { useRouter } from "next/router";
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const { asPath, push } = useRouter();
const [user, setUser] = useState(null);
// if you use trailing slash you'd need to add them to each route
const isAuthRoute = ["/login", "/signup", "/forgot", "/reset"].includes(asPath);
const redirectToLogin = useCallback(async () => {
try {
setUser(null);
await push('/login');
} catch (e) {
console.error("Could not redirect to login");
}
}, [push]);
const signOut = async () => {
try {
await authServiceSignout();
await redirectToLogin();
} catch {
window.location.reload();
}
};
const goHome = useCallback(() => push('/'), [push]);
// check if user is logged in on every route changes and
// redirect accordingly
useEffect(() => {
const getUser = async () => {
const user = await getUserOrJWT();
if (user) {
setUser(user); // user details
if (isAuthRoute) await goHome();
} else if (!isAuthRoute) {
await redirectToLogin();
}
};
getUser();
}, [asPath, goHome, isAuthRoute, redirectToLogin]);
if (!user && isAuthRoute) return <>{children}</>;
if (!user) return <>Loading or loading spinner</>;
return (
<AuthContext.Provider value={{ user, signOut }}>
{children}
</AuthContext.Provider>
);
};
I deleted a bunch of code so it probably doesn't work direct copy and paste but you can get the idea.
isAuthRoute only needs to return true/false so another good options if your dashboard routes all start with /dashboard you can use asPath.startsWith('/dashboard') instead.
We conditionally rendered the auth context because it's not needed on public pages and it also clears the user context on logout so we don't need to worry about leaking old user data via context.
We also use local storage and the window's broadcast channel to listen for logout calls. This allows us to log the user out in every tab and window.
After a lot of tests with different techniques, I decided to go for Next.js new middleware incredible feature.
If anybody struggles with this topic like I did, here is my code:
// _middleware.js in /pages, works also with Typescript .ts
import { NextResponse } from 'next/server';
import { isAuthValid } from '@/lib/auth'
export function middleware(req) {
if (
req.nextUrl.pathname.startsWith('/login') ||
req.nextUrl.pathname.startsWith('/signup') ||
req.nextUrl.pathname.startsWith('/forgot') ||
req.nextUrl.pathname.startsWith('/reset')
) {
if (isAuthValid(req)) {
return NextResponse.redirect(new URL('/profile', req.url));
}
return NextResponse.next();
}
// All other routes
if (isAuthValid(req)) {
return NextResponse.next();
}
return NextResponse.redirect(
new URL(`/login?from=${req.nextUrl.pathname}`, req.url)
);
}
Be careful though as this file name and location will change in new Next.js 12.2 version, under the name of middleware.js (or .ts) in your root folder, whether it's root or src depending on your configuration