Replies: 8 comments 16 replies
-
I suppose using the "old" approach using session directly still works in V5 and is the only workaround for the nexturl.clone issue: I've yet to fully test if this properly keeps the session alive. To me this seems to be a valid issue and potentially even a bug? I'm unsure what the authors intention is here - so I'll not mark this as a correct answer, until I can verify that this is a valid solution in V5. import { auth } from '@/auth/auth'
import { createI18nMiddleware } from 'next-international/middleware'
import { NextRequest, NextResponse } from 'next/server'
const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'da'],
defaultLocale: 'en',
})
console.log('NEXTAUTH_URL', process.env.NEXTAUTH_URL || undefined)
export default async function middleware(request: NextRequest) {
const session = await auth()
if (!session && request.nextUrl.pathname.includes('/projects')) {
return NextResponse.redirect(process.env.NEXTAUTH_URL as string)
}
return I18nMiddleware(request)
}
export const config = {
matcher: ['/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)'],
} |
Beta Was this translation helpful? Give feedback.
-
Combining NextAuth (v5) and Next-Intl middleware in a Next.js application can be efficiently achieved by chaining middleware functions. This approach allows you to manage authentication and internationalization in a modular and maintainable manner. Below is a step-by-step guide on how to implement this:
Step 1: Create a Chain UtilityFirst, create a // chain.ts
import { NextMiddlewareResult } from 'next/dist/server/web/types'
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export type CustomMiddleware = (
request: NextRequest,
event: NextFetchEvent,
response: NextResponse
) => NextMiddlewareResult | Promise<NextMiddlewareResult>
type MiddlewareFactory = (middleware: CustomMiddleware) => CustomMiddleware
export function chain(
functions: MiddlewareFactory[],
index = 0
): CustomMiddleware {
const current = functions[index]
if (current) {
const next = chain(functions, index + 1)
return current(next)
}
return (
request: NextRequest,
event: NextFetchEvent,
response: NextResponse
) => {
return response
}
} Step 2: Implement the Internationalization MiddlewareCreate a file // withI18nMiddleware.ts
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
import { i18n } from '@/i18n.config'
import { match as matchLocale } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import { CustomMiddleware } from './chain'
function getLocale(request: NextRequest): string | undefined {
const negotiatorHeaders: Record<string, string> = {}
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value))
// @ts-ignore locales are readonly
const locales: string[] = i18n.locales
const languages = new Negotiator({ headers: negotiatorHeaders }).languages()
const locale = matchLocale(languages, locales, i18n.defaultLocale)
return locale
}
export function withI18nMiddleware(middleware: CustomMiddleware) {
return async (
request: NextRequest,
event: NextFetchEvent,
response: NextResponse
) => {
const pathname = request.nextUrl.pathname
const pathnameIsMissingLocale = i18n.locales.every(
locale => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
)
if (pathnameIsMissingLocale) {
const locale = getLocale(request)
return NextResponse.redirect(
new URL(
`/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
request.url
)
)
}
return middleware(request, event, response)
}
} Step 3: Implement the Authentication MiddlewareCreate a file // withAuthMiddleware.ts
import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
import type { NextFetchEvent, NextRequest } from 'next/server';
import { CustomMiddleware } from './chain';
export function withAuthMiddleware(middleware: CustomMiddleware): CustomMiddleware {
return async (request: NextRequest, event: NextFetchEvent, response: NextResponse) => {
const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET });
if (!token) {
return NextResponse.redirect(new URL('/api/auth/signin', request.url));
}
return middleware(request, event, response);
};
} Step 4: Combine the MiddlewaresCreate a // middleware.ts
import { chain } from '@/middlewares/chain'
import { withI18nMiddleware } from '@/middlewares/withI18nMiddleware'
import { withAuthMiddleware } from '@/middlewares/withAuthMiddleware'
export default chain([withI18nMiddleware, withAuthMiddleware])
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|images).*)'],
} Explanation
By following these steps, you can efficiently combine NextAuth and Next-Intl middleware in your Next.js application, ensuring both authentication and internationalization are seamlessly handled. |
Beta Was this translation helpful? Give feedback.
-
This one works on me..
import type { NextAuthConfig } from 'next-auth';
import {apiRoutePrefix, authRoutes, DEFAULT_LOGIN_REDIRECT, privateRoutes, publicRoutes} from "@/route";
export const authConfig = {
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isPrivateRoute = privateRoutes.includes(nextUrl.pathname)
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isApiAuthRoute = nextUrl.pathname.startsWith(apiRoutePrefix)
if(isPublicRoute || isApiAuthRoute) return true;
if (isPrivateRoute) {
if (isLoggedIn) {
return true;
}
return Response.redirect(new URL('/auth/login', nextUrl));
} else if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl));
}
return true;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
import NextAuth from "next-auth";
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import {authConfig} from "@/auth.config";
// export default NextAuth(authConfig).auth;
const {auth} = NextAuth(authConfig)
export async function logRequest(req: NextRequest, res: NextResponse, next: Function) {
console.log(`My Request URL: ${req.url}`);
next();
}
function combineMiddleware(...middlewares: Function[]) {
return async (req: NextRequest) => {
console.log("Reached the combined middleware");
for (const middleware of middlewares) {
const result = await middleware(req, NextResponse.next(), () => {});
if (result instanceof Response || result instanceof NextResponse) {
return result;
}
}
return NextResponse.next();
};
}
export default combineMiddleware(
logRequest,
auth
);
export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
}
import {PrismaAdapter} from "@auth/prisma-adapter";
import Credentials from 'next-auth/providers/credentials';
import {db} from "@/lib/db"
import {LoginSchema} from "@/schemas";
import {getUserByEmail} from "@/repositories/user";
import {matchPassword} from "@/lib/passwordUtils";
export const {
handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
session: {strategy: "jwt"},
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const validatedFields = LoginSchema.safeParse(credentials)
if(validatedFields.success){
const {email, password} = validatedFields.data
const user = await getUserByEmail(email)
if(!user || !user.password) return null
const passwordMatch = await matchPassword(password, user.password)
if(passwordMatch) return user;
}
return null;
},
}),
]
}) |
Beta Was this translation helpful? Give feedback.
-
NOTE: See our updated post further below. We also took @TanzimHossain2 's approach above... thanks to this repo - [https://reacthustle.com/blog/how-to-chain-multiple-middleware-functions-in-nextjs and the source repo here ](https://github.com/jmarioste/next-middleware-guide) Our import { NextMiddleware, NextResponse } from 'next/server'
import { MiddlewareFactory } from './@types'
export function chainMiddleware(functions: MiddlewareFactory[] = [], index = 0): NextMiddleware {
const current = functions[index]
if (current) {
const next = chainMiddleware(functions, index + 1)
return current(next)
}
return () => NextResponse.next()
} And then we have a list of middleware functions that we can call in // NOTE: withI18N must come last.
export default chainMiddleware([withNonce, withCSP, withPrefersColorScheme, withAuth, withI18n]) Our import NextAuth from 'next-auth'
import { NextFetchEvent, NextMiddleware, NextRequest, NextResponse } from 'next/server'
import { defaultLocale } from '@/i18n/settings'
import { authConfig } from '../../auth.config'
const { auth } = NextAuth(authConfig)
import { getLocale } from './withI18n/get-locale'
import type { MiddlewareFactory } from './@types'
function containsDashboard(value: string) {
const regex = /^(\/[a-zA-Z]{2})?\/dashboard(\/.*)?$/
return regex.test(value)
}
export const withAuth: MiddlewareFactory = (next: NextMiddleware) => {
return async (request: NextRequest, _next: NextFetchEvent) => {
const { nextUrl } = request
const locale = getLocale(request)
const session = await auth()
console.log('withAuth', session)
if (session == null && containsDashboard(nextUrl.pathname)) {
const url =
locale !== defaultLocale
? `/${locale}/sign-in?callbackUrl=${encodeURIComponent(nextUrl.pathname)}`
: `/sign-in?callbackUrl=${encodeURIComponent(nextUrl.pathname)}`
return NextResponse.redirect(new URL(url, request.url))
}
if (session != null && containsDashboard(nextUrl.pathname) === false) {
const url = locale !== defaultLocale ? `/${locale}/dashboard` : `/dashboard`
return NextResponse.redirect(new URL(url, request.url))
}
return next(request, _next)
}
} However, it took a LOT of research to get to this, and the docs here https://authjs.dev/getting-started/migrating-to-v5?authentication-method=middleware#authentication-methods - are not particularly helpful. What's more - this is effectively a manual implementation of auth middleware, and so we don't know if this is effectively what the built-in auth strategy would have done..... with export const { auth: middleware } = NextAuth(authConfig) Or const { auth } = NextAuth(authConfig)
export default auth(async function middleware(req: NextRequest) {
// Your custom middleware logic goes here
}) Would be great if there was a bit more information in the docs. |
Beta Was this translation helpful? Give feedback.
-
NOTE: See our updated post below. Here's a link to our complete middleware solution.... although again, it would be great if auth.js could change the signature of the optional function to allow chaining... https://www.58bits.com/blog/chaining-or-combining-nextjs-middleware |
Beta Was this translation helpful? Give feedback.
-
Hey all - we've updated our middleware chaining strategy quite a bit, fixing a few mistakes above, creating a new chainMiddleware factory, and new middleware plugin signature. We now correctly pass the request object to any rewrites. Here's our updated solution... https://www.58bits.com/blog/chaining-or-combining-nextjs-middleware Our // https://reacthustle.com/blog/how-to-chain-multiple-middleware-functions-in-nextjs
// https://github.com/jmarioste/next-middleware-guide/
import { NextResponse } from 'next/server'
import { ChainableMiddleware, MiddlewareFactory as MiddlewareFactory } from './@types'
/**
* Helper to compose multiple MiddlewareFactory instances together.
*
* We restrict the type of middleware to ChainableMiddleware, which is a strict
* subset of the full NextMiddleware type. Specifically, we require middleware
* layers to return Promise<NextResponse>, as opposed to a few other return
* types that NextMiddleware allows in general. This restriction allows
* middleware layers that want to set response cookies to do so using the
* NextResponse cookies api: each layer reliably receives a NextResponse from
* the next layer in the chain, and can add cookies to it before passing it on
* down.
*
* Important: layers must construct NextResponses (specifically the .next() and
* .rewrite() variants) by passing in the (possibly mutated) request object.
* Then any headers/cookies that have been set on the request object (including
* by earlier layers) will be properly passed on to the Next.js request
* handlers: page components, server actions and route handlers.
*/
export function chainMiddleware(
functions: MiddlewareFactory[] = [],
index = 0
): ChainableMiddleware {
const current = functions[index]
if (current) {
const next = chainMiddleware(functions, index + 1)
return current(next)
}
return async (request) => NextResponse.next({ request })
} Our import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'
export type ChainableMiddleware = (
request: NextRequest,
event: NextFetchEvent
) => Promise<NextResponse>
export type MiddlewareFactory = (middleware: ChainableMiddleware) => ChainableMiddleware And here's an example plugin.... // https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy
import { NextFetchEvent, NextRequest } from 'next/server'
import { MiddlewareFactory } from './@types'
import { getConfig, getCSPHeader } from '@/config'
export const withCSP: MiddlewareFactory = (next) => {
const config = getConfig()
return async (request: NextRequest, event: NextFetchEvent) => {
if (config.cspEnabled && request.headers.get('x-nonce') != null) {
const nonce = request.headers.get('x-nonce')
const cspHeader = getCSPHeader(nonce)
// TODO: Should this be set on the request?
request.headers.set('Content-Security-Policy', cspHeader)
const response = await next(request, event)
response.headers.set('Content-Security-Policy', cspHeader)
return response
} else {
return next(request, event)
}
}
} |
Beta Was this translation helpful? Give feedback.
-
don't use the |
Beta Was this translation helpful? Give feedback.
-
Why is this so hard? Why doesn't Next.JS have a built-in middleware chaining function... |
Beta Was this translation helpful? Give feedback.
-
Summary
The current documentation (V5) only highlights one approach for creating the middleware.
I'm unable to see how this can be chained with other libraries middleware's that require the original NextRequest object - in my case Next-International - as the middleware provided by authjs operates with the NextAuthRequest object. Result is error: req.NextUrl.clone is not a function.
Any ideas as to what am I missing/overlooking here?
Thanks in advance.
Edit #1
Searched the discussion forum more and found the following in the v5 discussion. I was not aware this was a way to do further work with the request, although here the NextUrl.clone issue seems to persist.
Additional information
Example
No response
Beta Was this translation helpful? Give feedback.
All reactions