Reputation: 91
I don't know how to use 'next-auth/middleware' and 'next-intl/middleware' together in the middleware. Next-auth gets exported as default and it's a must... on the other hand Next-intl creates a middleware and add a separate config...
export { default } from "next-auth/middleware";
export const config = {
matcher: ["/((?!register|api|login).*)"],
};
import createMiddleware from "next-intl/middleware";
export default createMiddleware({
locales: ["en", "es"],
defaultLocale: "en",
});
export const config = {
// Skip all paths that should not be internationalized
matcher: ["/((?!api|_next|.*\\..*).*)"],
};
I tried to search on google to use these two together but there was no blog. I don't know how these two are going to be in a single middleware export
Upvotes: 9
Views: 16807
Reputation: 735
(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
middleware
's function, if user isn't trying to access protected routes;or
authMiddleware
after it has completed its job.My code covers 3 typical usecases:
/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
}
Upvotes: 0
Reputation: 41
I've made a package for a chain of middlewares - @nimpl/middleware-chain. Unlike the examples above, it can help in situations when you need next-intl to work and in private routes.
For example, in this code, next-intl works first and, if necessary, redirects to the localized route (you don't want to check authorization before the redirect, because otherwise, you will need to check it before and after the redirect). Then next-auth works.
import { default as nextAuth } from "next-auth/middleware";
import createMiddleware from "next-intl/middleware";
import { chain, FinalNextResponse } from "@nimpl/middleware-chain";
const intlMiddleware = createMiddleware({
locales: ["en", "dk"],
defaultLocale: "en",
});
export default chain([
intlMiddleware,
(req) => {
if (req.summary.type === "redirect") return FinalNextResponse.next();
},
nextAuth,
]);
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Full example - https://github.com/vordgi/nimpl-middleware-chain/blob/main/examples/auth-intl/src/middleware.ts
Upvotes: 3
Reputation: 61
The following worked for me with next-auth 5.0.0-beta.17 and next-intl 3.12.2:
import { NextRequest, NextResponse } from "next/server";
import createIntlMiddleware from "next-intl/middleware";
import { localePrefix, locales, publicPages } from "./navigation";
import { auth, BASE_PATH } from "@/auth";
const intlMiddleware = createIntlMiddleware({
locales,
localePrefix,
defaultLocale: "en",
});
const authMiddleware = auth((req) => {
if (req.auth) return intlMiddleware(req);
const reqUrl = new URL(req.url);
if (!req.auth && reqUrl?.pathname !== "/") {
return NextResponse.redirect(
new URL(`${BASE_PATH}/signin?callbackUrl=${encodeURIComponent(reqUrl?.pathname)}`, req.url)
);
}
});
export default function middleware(req: NextRequest) {
const publicPathnameRegex = RegExp(
`^(/(${locales.join("|")}))?(${publicPages
.flatMap((p) => (p === "/" ? ["", "/"] : p))
.join("|")})/?$`,
"i"
);
const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname);
if (isPublicPage) {
return intlMiddleware(req);
} else {
return (authMiddleware as any)(req);
}
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
The key is to make export middleware to return intlMiddleware(req) if req.auth.
if (req.auth) return intlMiddleware(req);
Upvotes: 6
Reputation: 1069
import { getToken } from "next-auth/jwt";
import { withAuth } from "next-auth/middleware";
import { createI18nMiddleware } from "next-international/middleware";
const I18nMiddleware = createI18nMiddleware({
locales: ["en", "fa"],
defaultLocale: "fa",
});
const withAuthMiddleware = withAuth(
async function middleware(req) {
const token = await getToken({ req });
const isAuth = !!token;
const isAuthPage =
req.nextUrl.pathname.startsWith("/login") ||
req.nextUrl.pathname.startsWith("/register");
if (isAuthPage) {
if (isAuth) {
return I18nMiddleware(req);
// return NextResponse.redirect(new URL("/dashboard", req.url));
}
return null;
}
if (!isAuth) {
let from = req.nextUrl.pathname;
if (req.nextUrl.search) {
from += req.nextUrl.search;
}
return I18nMiddleware(req);
// return NextResponse.redirect(
// new URL(`/login?from=${encodeURIComponent(from)}`, req.url),
// );
}
},
{
callbacks: {
async authorized() {
// This is a work-around for handling redirect on auth pages.
// We return true here so that the middleware function above
// is always called.
return true;
},
},
},
);
export default withAuthMiddleware;
export const config = {
matcher: [
"/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)",
],
};
Upvotes: 0
Reputation: 31
Wrapping the intl middelware with withAuth seemed to work for me:
import { withAuth } from 'next-auth/middleware';
import createMiddleware from 'next-intl/middleware';
const intlMiddelware = createMiddleware({
locales: ['en'],
defaultLocale: 'en',
});
export default withAuth(function middleware(req) {
return intlMiddelware(req);
});
Upvotes: 1
Reputation: 1049
Here at: Composing other middlewares Nextintl you can find more details.
import {withAuth} from 'next-auth/middleware';
import createIntlMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
const locales = ['en', 'de'];
const publicPages = ['/', '/login'];
const intlMiddleware = createIntlMiddleware({
locales,
defaultLocale: 'en'
});
const authMiddleware = withAuth(
// Note that this callback is only invoked if
// the `authorized` callback has returned `true`
// and not for pages listed in `pages`.
function onSuccess(req) {
return intlMiddleware(req);
},
{
callbacks: {
authorized: ({token}) => token != null
},
pages: {
signIn: '/login'
}
}
);
export default function middleware(req: NextRequest) {
const publicPathnameRegex = RegExp(
`^(/(${locales.join('|')}))?(${publicPages.join('|')})?/?$`,
'i'
);
const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname);
if (isPublicPage) {
return intlMiddleware(req);
} else {
return (authMiddleware as any)(req);
}
}
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)']
};
Upvotes: 5
Reputation: 41
There is a new examples:
https://next-intl-docs.vercel.app/docs/next-13/middleware#example-auth-js
However, make sure "next" version is 13.4.6 and up. For example:
npm install [email protected]
Upvotes: 4