-
-
Notifications
You must be signed in to change notification settings - Fork 231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for NextAuth v5 multiple middleware #596
Comments
I currently don't have experience with v5. We have an example within this repo as well as documentation on usage with v4. Would you be interested in setting up a pull request with an update to v5? We have a few automated tests in example-next-13-next-auth that can help to verify if the updated code works as expected. As far as I know, v5 is in beta currently though. We should wait until v5 becomes stable before updating our example in the |
Sure thing! I just made a PR, but my PR skills are a bit rusty so bear with me 🙂 |
I am trying to make it work but apparently, this does not work anymore
So I decided to follow the next-auth and next.js guidelines about this and I came up with this:
But the thing is this does not work either... |
As a temporary workaround, you can set a custom header in the middleware, and handle redirection in the main layout. In middleware.tsx export async function middleware(request: NextRequest) {
const intlMiddleware: (request: NextRequest) => NextResponse<unknown> = createMiddleware({
locales: ['en', 'dk'],
defaultLocale: 'en'
});
const session = await nextAuth(NextAuthConfiguration).auth();
if (!session) {
request.headers.set('x-auth-unauthorized', 'true');
}
return intlMiddleware(request);
} In layout.tsx const headersList = headers();
const unauthorized = headersList.get('x-auth-unauthorized') === 'true'; |
Thanks, I'm using Supabase and I was having problems as Supabase uses a middleware too. Here's my middleware.ts import createMiddleware from 'next-intl/middleware';
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs';
import { NextResponse, NextRequest } from 'next/server';
export async function middleware(req: NextRequest) {
const intlMiddleware: (request: NextRequest) => NextResponse<unknown> =
createMiddleware({
locales: ['en', 'es'],
defaultLocale: 'en',
});
const res = NextResponse.next();
const supabase = createMiddlewareClient({ req, res });
const {
data: { user },
} = await supabase.auth.getUser();
if (!user && req.nextUrl.pathname.includes('account')) {
return NextResponse.redirect(new URL('auth', req.url));
}
return intlMiddleware(req);
}
export const config = {
matcher: ['/', '/(en|es)/:path*'],
}; |
Any update on this? I'm at the same point, getting this error: And in the server:
The concrete line is this, if it helps UPDATE: It seems to be a problem with the |
What worked for me was to use the const intlMiddleware = createIntlMiddleware({
locales,
defaultLocale,
localePrefix: 'never', // we do not need the path since we are not worried about SEO
localeDetection: true,
})
export default async function middleware(req: NextRequest) {
// Init intl middleware response
const intlResponse = intlMiddleware(req)
// APIs handle their own auth, but they still need locale data
if (req.nextUrl.pathname.includes('api')) {
return intlResponse
}
// handle public routes...
// private routes here
const session = await auth()
if(session !== null) {
return intlResponse
}
// redirect to sign in if unauthorized
const nextUrl = req.nextUrl.clone()
nextUrl.pathname = '/auth/signin'
// When using redirect Next.js expects a 3xx status code otherwise it errors out
return NextResponse.redirect(nextUrl, { ...intlResponse, status: HttpCodes.PERMANENT_REDIRECT })
}
export const config = { matcher: ['/((?!_next|.*\\..*).*)'] } Note that we are not using the locale prefix in the pathname as we do not need it, so if you do this logic might change a bit but the general idea should still work. |
There is a way how to solve the issue, maybe it's not the most elegant solution, but it works as expected: interface AppRouteHandlerFnContext {
params?: Record<string, string | string[]>;
}
const i18nMiddleware = createI18nMiddleware({
locales: ['en', 'fr'],
defaultLocale: 'en',
urlMappingStrategy: 'rewriteDefault',
});
export const middleware = (request: NextRequest, event: AppRouteHandlerFnContext): NextResponse => {
return NextAuth(authConfig).auth(() => {
return i18nMiddleware(request);
})(request, event) as NextResponse;
};
export const config = {
matcher: [ '/', '/(en|fr)/:path*', '/((?!api|_next/static|_next/image|.png).*)'],
}; Passing the callback into auth middleware we receive not the original request object and it's a big problem because it doesn't have headers and cookies fields. |
This worked for me "next": "^14.1.0",
"react": "18.2.0",
"next-auth": "5.0.0-beta.13",
"next-intl": "^3.4.2", import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
import createIntlMiddleware from "next-intl/middleware"
import { auth } from "@/auth"
import { defaultLocale, localePrefix, locales } from "./messages/config"
const publicPages = [
"/",
"/unauthorized",
"/pricing",
"/roadmap",
"/features",
"/contact",
"/about",
"/help",
"/privacy-policy",
"/terms-of-service",
"/cookie-policy",
"/end-user-license-agreement"
]
const authPages = ["/sign-in", "/sign-up"]
const testPathnameRegex = (pages: string[], pathName: string): boolean => {
return RegExp(
`^(/(${locales.join("|")}))?(${pages.flatMap((p) => (p === "/" ? ["", "/"] : p)).join("|")})/?$`,
"i"
).test(pathName)
}
const intlMiddleware = createIntlMiddleware({
locales,
defaultLocale,
localePrefix
})
const authMiddleware = auth((req) => {
const isAuthPage = testPathnameRegex(authPages, req.nextUrl.pathname)
const session = req.auth
// Redirect to sign-in page if not authenticated
if (!session && !isAuthPage) {
return NextResponse.redirect(new URL("/sign-in", req.nextUrl))
}
// Redirect to home page if authenticated and trying to access auth pages
if (session && isAuthPage) {
return NextResponse.redirect(new URL("/", req.nextUrl))
}
return intlMiddleware(req)
})
const middleware = (req: NextRequest) => {
const isPublicPage = testPathnameRegex(publicPages, req.nextUrl.pathname)
const isAuthPage = testPathnameRegex(authPages, req.nextUrl.pathname)
if (isAuthPage) {
return (authMiddleware as any)(req)
}
if (isPublicPage) {
return intlMiddleware(req)
} else {
return (authMiddleware as any)(req)
}
}
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)"]
}
export default middleware |
I found this article wich is about to chain multiple middleware. I didn't try it yet, but what do you think about it, i think this would be the greate solution, to chain up multiple middlewares so you can extend your application as you need. https://reacthustle.com/blog/how-to-chain-multiple-middleware-functions-in-nextjs |
@0x963D This is great thank you! Not specific to this code necessarily I am trying this out with next-auth v5 on with next-intl on aws amplify and having the most odd issue, this code works great does the redirection correctly but the second I hit a localized route where next-intl middleware is providing the NextResponse I get into a redirect loop where it requests the same page over and over again. It's not a specific route but any localized route returned by next-intl middleware either before auth or after successful auth doesn't seem to matter matter. As soon as I get a response from next-intl it's massive 307 spam as the middleware redirects the current page to itself. Did I do something wrong here in the code to get into this loop? or is this an amplify thing? next@14.2.4 import { NextRequest, NextResponse } from "next/server"
import { auth } from "auth"
import createMiddleware from "next-intl/middleware"
import { locales } from "@/config/locales"
import { localePrefix, pathnames } from "./navigation"
/*
* Behind the scenes, NextAuth.js uses Next.js API Routes to handle requests to /api/auth/* and provides the following routes:
* GET /api/auth/signin
* POST /api/auth/signin/:provider
* GET/POST /api/auth/callback/:provider
* GET /api/auth/signout
* POST /api/auth/signout
* GET /api/auth/session
* GET /api/auth/csrf
* GET /api/auth/providers
*/
// Define the public pages that should be accessible without authentication
const publicPages = ["/help"]
const authPages = ["/login", "/sign-up"]
const testPathnameRegex = (pages: string[], pathName: string): boolean => {
return RegExp(
`^(/(${locales.join("|")}))?(${pages.flatMap((p) => (p === "/" ? ["", "/"] : p)).join("|")})/?$`,
"i"
).test(pathName)
}
const intlMiddleware = createMiddleware({
// A list of all locales that are supported
locales,
pathnames,
localePrefix,
// If this locale is matched, pathnames work without a prefix (e.g. `/about`)
defaultLocale: "en-US",
localeDetection: true,
})
const authMiddleware = auth((req) => {
const isAuthPage = testPathnameRegex(authPages, req.nextUrl.pathname)
const session = req.auth
// Redirect to sign-in page if not authenticated
if (!session && !isAuthPage) {
return NextResponse.redirect(new URL("/login", req.nextUrl))
}
// Redirect to home page if authenticated and trying to access auth pages
if (session && isAuthPage) {
return NextResponse.redirect(new URL("/", req.nextUrl))
}
return intlMiddleware(req)
})
// https://github.com/vercel/next.js/blob/canary/examples/with-strict-csp/middleware.js#L4
// allow access if the nonce in the content security policy matches the x-nonce header
// script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
// style-src 'self' 'nonce-${nonce}';
const middleware = (request: NextRequest) => {
// // allow public access to next-auth routes for signin, signout, etc.
if (request.nextUrl.pathname.startsWith("/auth")) {
return NextResponse.next({
// headers: requestHeaders,
})
}
const isAuthPage = testPathnameRegex(authPages, request.nextUrl.pathname)
const isPublicPage = testPathnameRegex(publicPages, request.nextUrl.pathname)
if (isAuthPage) {
return (authMiddleware as any)(request)
}
if (isPublicPage) {
return intlMiddleware(request)
} else {
return (authMiddleware as any)(request)
}
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)", "/"],
}
export default middleware |
the key point is next-intl must use original request, not auth req: import { auth } from '@/auth';
import createIntlMiddleware from 'next-intl/middleware';
import { defaultLocale, localePrefix, locales } from '@/lib/i18n/i18nConfig';
import { NextRequest, NextResponse } from 'next/server';
interface AppRouteHandlerFnContext {
params?: Record<string, string | string[]>;
}
const intlMiddleware = createIntlMiddleware({
locales,
defaultLocale,
localePrefix
});
const homePages = ['/', '/signin'];
const authPages = [
'/dashboard',
'/dashboard/post',
'/dashboard/analytics',
'/dashboard/followers'
];
const getPathnameRegex = (pages: string[]) =>
RegExp(
`^(/(${locales.join('|')}))?(${pages
.flatMap((p) => (p === '/' ? ['', '/'] : p))
.join('|')})/?$`,
'i'
);
const homePathnameRegex = getPathnameRegex(homePages);
const authPathnameRegex = getPathnameRegex(authPages);
const authMiddleware = (
request: NextRequest,
ctx: AppRouteHandlerFnContext
) => {
return auth((req) => {
const path = req.nextUrl.pathname;
const isAuth = req.auth;
const isHomePage = homePathnameRegex.test(path);
const isAuthPage = authPathnameRegex.test(path);
if (isAuth && isHomePage) {
return NextResponse.redirect(new URL('/dashboard', req.url));
} else if (!isAuth) {
if (isAuthPage) {
return NextResponse.redirect(new URL('/signin', req.url));
}
}
return intlMiddleware(request);
})(request, ctx);
};
export const middleware = (
request: NextRequest,
ctx: AppRouteHandlerFnContext
): NextResponse => {
if (request.nextUrl.pathname.startsWith('/auth')) {
return NextResponse.next();
}
return authMiddleware(request, ctx) as NextResponse;
};
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
}; |
@fkysly I have seen the code of the implementation. Thank you so much. I do have one question, though. According to the Next.js documentation, the second argument of the middleware should be NextFetchEvent. In your implementation, however, it is AppRouteHandlerFnContext, and it seems that the TypeScript types are not compatible. Could you please clarify if this adjustment was made to align with the auth function’s type definition? Is this a safe assumption? |
I have had the same problem. And it has been a complex thing to solve. In my case I used this example: https://github.com/hugocruzlfc/nextjs-with-authjs-and-next-intl As a basis, I even followed the same pattern for my current company, including Prisma. The headache is introduced by the middleware configuration. I hope it helps. Good luck to you. |
I used your next-intl setup but when i invoke getNow() or getLocale() methods in server side of my app i recieve the error bellow: Error: Unable to find middleware.ts
I tried a lot of things to solve but i cant find the problem. |
Is your feature request related to a problem? Please describe.
I use both next-intl and next-auth middleware. There are some nice examples of this combination for next-auth v4 (
withAuth()
), but not for v5 when usingauth
.Would love to see.
Describe the solution you'd like
I imagine the usage something like this:
Describe alternatives you've considered
Alternatively I can stick with using next-auth v4
The text was updated successfully, but these errors were encountered: