(Using Next-auth V4)
Here is a commented solution to understand how things operate under the hood according to my interpretation. (Please do correct me if I am wrong)
Basically, the middleware function is called whenever your request's route matches the config expression.
It is the middleware's function responsibility to handle your request and/or dispatch it to Next-intl's or Next-auth's middlewares based on set conditions.
Note that – in this code – intlMiddleware is always called:
either
- first, via
middleware's function, if user isn't trying to access protected routes;
or
- second, via
authMiddleware after it has completed its job.
My code covers 3 typical usecases:
- If authenticated user tries to access
/login or /register (or /fr/inscription or /sv/inloggning...etc) -> We redirect to / or whatever (Neither intlMiddleware nor authMiddleware are called at this stage);
/!\ As ARiyou Jahan's post shows, this redirection can also be done within
authMiddleware but requires to check if user is authenticated
in two different scopes.
if user tries to access public routes (excluding /login and /register) -> We dispatch the request to intlMiddleware to work its localisation magic (authMiddleware is completely skipped);
if user tries to access protected route -> We dispatch the request to authMiddleware to ensure that user is authenticated:
Unauthenticated user : this middleware force-redirects to
/login (or to whatever the signIn property points to) then
automatically redirects back to the protected route after
successful login. intlMiddleware is called here, as /login route in translated when user is force-redirected;
authenticated user : this middleware simply transfers the request to intlMiddleware without blocking the user from accessing the protected route.
import createMiddleware from 'next-intl/middleware'
import withAuth, { NextRequestWithAuth } from 'next-auth/middleware'
import { NextFetchEvent, NextResponse } from 'next/server'
import { isLoginOrRegisterPath, isProtectedPath} from './utils/paths'
import {
defaultLocale,
localePrefix,
locales,
pathnames,
} from './lib/i18n/navigation'
import { LOGIN_ROUTE } from './routes/routes'
import { getToken } from 'next-auth/jwt'
import { Pathname } from './types/pathnames'
const intlMiddleware = createMiddleware({
defaultLocale,
localePrefix,
locales,
pathnames,
})
// AuthMiddleware is skipped if user hits public routes
const authMiddleware = withAuth(
function onSuccess(req) {
// Skip to IntlMiddleware if user is authenticated
return intlMiddleware(req)
},
{
callbacks: {
authorized: ({ token }) => token !== null, // User is considered authenticated if token isn't null
},
pages: {
// Replace Next-auth's built-in pages with own custom pagess
signIn: LOGIN_ROUTE, // Unauthenticated user is redirected here when attempting to access protected routes
error: '/error',
},
}
)
// Called whenever request matches config
// Dispatches request to middlewares depending on conditions
export default async function middleware(req: NextRequestWithAuth) {
const { pathname } = req.nextUrl
// Can be replaced with isPublicRoute, if application mainly contains protected routes
const isProtectedRoute = isProtectedPath(pathname as Pathname)
const session = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET, // Needed outside of authMiddleware to access session (has to be the same as in authOptions at /auth.config.ts)
})
const isConnected = !!session
// Prevent authenticated user from reaching login/register pages by redirecting to homepage
if (isConnected && isLoginOrRegisterPath(pathname as Pathname)) {
return NextResponse.redirect(new URL('/', req.url))
}
// Skip authMiddleware if route is public
return isProtectedRoute
? authMiddleware(req, {} as NextFetchEvent)
: intlMiddleware(req)
}
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)'], // Middleware triggers whenever route matches this regex
}