-
Notifications
You must be signed in to change notification settings - Fork 10
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
Combine createMiddleware with additional auth middleware #2639
Comments
Hey @brandanking-decently - our // This example is for Auth.js 5, the successor to NextAuth 4
import arcjet, { createMiddleware, shield } from "@arcjet/next";
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
// @ts-ignore
import type { NextAuthConfig, NextAuthRequest } from "next-auth";
export const config = {
// matcher tells Next.js which routes to run the middleware on.
// This runs the middleware on all routes except for static assets.
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
const aj = arcjet({
key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
rules: [
// Protect against common attacks with Arcjet Shield
shield({
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
}),
],
});
export const authConfig = {
providers: [GitHub],
} satisfies NextAuthConfig;
const { auth } = NextAuth(authConfig);
export const authMiddleware = auth(async (req: NextAuthRequest) => {
if (!req.auth) {
// If the user is not authenticated, return a 401 Unauthorized response. You
// may wish to redirect to a login page instead.
return Response.json({ message: "Unauthorized" }, { status: 401 });
}
});
export default createMiddleware(aj, authMiddleware); For multiple middleware then you may want to use a helper library like https://nemo.rescale.build Have you tried either of these approaches? Did you have any problems? |
I was trying to use the @nosecone/next package which doesn't seem to accept another middleware into it? Is this achievable in that package? |
Ah, for Nosecone you would need to call it within your own custom middleware function. If you're using Auth.js with Next.js then you could create a import { type NoseconeOptions, createMiddleware, defaults } from "@nosecone/next";
import { auth } from "auth";
// Nosecone security headers configuration
// https://docs.arcjet.com/nosecone/quick-start
const noseconeOptions: NoseconeOptions = {
...defaults,
};
const securityHeaders = createMiddleware(noseconeOptions);
export default auth(async (req) => {
if (!req.auth && !req.nextUrl.pathname.startsWith("/auth")) {
const newUrl = new URL("/auth/signin", req.nextUrl.origin)
return Response.redirect(newUrl)
}
return securityHeaders();
})
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
} This assumes Auth.js 5 beta and that the auth API path is Does that work? |
My middleware looks like this, we also use Next Intl to handle i18n. Not sure this is possible to chain currently export default auth(
async function middleware(request: KindeRequest) {
return intlMiddleware(request);
},
); |
Chaining more than 2 layers of middleware gets fiddly, so I'd suggest using https://nemo.rescale.build/ to have Nosecone run "before", then set up auth, and have the intlMiddleware run "after". |
I believe I was able to get it close to working based on what you said, however, whenever I use the headerMiddleware (needed to rename as multiple createMiddleware functions) I now always get a 404. Do you have any idea what might be causing it? const intlMiddleware = createIntlMiddleware(routing);
const noseconeOptions: NoseconeOptions = {
...defaults,
};
const before: MiddlewareFunction[] = [headerMiddleware(noseconeOptions)];
const after: MiddlewareFunction[] = [
async ({ request }: MiddlewareFunctionProps) => {
return withAuth(request, { isReturnToCurrentPage: true, publicPaths: ['/'] });
},
async ({ request }: MiddlewareFunctionProps) => {
return intlMiddleware(request);
},
];
export const middleware = createMiddleware({}, { before, after });
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] }; |
Can you provide the imports and package.json so I can see which packages these are coming from please? |
import { withAuth } from '@kinde-oss/kinde-auth-nextjs/middleware';
import { type NoseconeOptions, defaults, createMiddleware as headerMiddleware } from '@nosecone/next';
import { type MiddlewareFunction, type MiddlewareFunctionProps, createMiddleware } from '@rescale/nemo';
import createIntlMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
const intlMiddleware = createIntlMiddleware(routing);
const noseconeOptions: NoseconeOptions = {
...defaults,
};
const before: MiddlewareFunction[] = [headerMiddleware(noseconeOptions)];
const after: MiddlewareFunction[] = [
async ({ request }: MiddlewareFunctionProps) => {
return withAuth(request, { isReturnToCurrentPage: true, publicPaths: ['/'] });
},
async ({ request }: MiddlewareFunctionProps) => {
return intlMiddleware(request);
},
];
export const middleware = createMiddleware({}, { before, after });
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] }; |
I had a play around with Nemo and couldn't get it to work properly either. We have an internal tool that handles the basic Nemo functionality to chain middleware, so I've tidied that up here. Can you try this please: 1. import { type NoseconeOptions, defaults, createMiddleware as noseconeMiddleware } from "@nosecone/next";
import {
type NextFetchEvent,
type NextMiddleware,
type NextRequest,
NextResponse,
} from "next/server";
import { match } from "path-to-regexp";
// Next.js middleware config
export const config = {
matcher: ['/((?!_next/|_static|_vercel|[\\w-]+\\.\\w+).*)'],
};
// Nosecone security headers configuration
// https://docs.arcjet.com/nosecone/quick-start
const noseconeOptions: NoseconeOptions = {
...defaults,
};
const securityHeaders = noseconeMiddleware(noseconeOptions);
// Add any paths you want to run different middleware for. They use
// path-to-regexp which is the same as the Next.js config. You can provide a
// single middleware or an array of middlewares.
export default router({
// Run nosecone middleware on any path
"/{*path}": [securityHeaders],
});
// Simplified version of nemo. This could be extracted into a utility library
function router(
pathMiddlewareMap: Record<string, NextMiddleware | NextMiddleware[]>,
): NextMiddleware {
const middleware = Object.entries(pathMiddlewareMap).map(
([path, middleware]) => {
if (Array.isArray(middleware)) {
return [match(path), middleware] as const;
} else {
return [match(path), [middleware]] as const;
}
},
);
return async (
request: NextRequest,
event: NextFetchEvent,
): Promise<NextResponse | Response> => {
const path = request.nextUrl.pathname || "/";
const addedHeaders = new Headers();
for (const [matchFunc, middlewareFuncs] of middleware) {
const m = matchFunc(path);
if (m) {
for (const fn of middlewareFuncs) {
const resp = await fn(request, event);
// TODO: better response guards
if (typeof resp !== "undefined" && resp !== null) {
resp.headers.forEach((value, key) => {
addedHeaders.set(key, value);
});
}
}
}
}
addedHeaders.set("x-middleware-next", "1");
return new Response(null, {
headers: addedHeaders,
});
};
}
export default router({
// Run nosecone middleware on any path
"/{*path}": [securityHeaders, intlMiddleware]
}); If this works for you then I'll update our docs to include it as a proper example. |
Unfortunately I just can't seem to get this working with the auth middleware as well. Thank you for your help on this however. |
What happens when you use the code in #2639 (comment) ? If you can describe what’s happening and show your current implementation I can try and reproduce it. |
Below is my implementation, I just keep getting sent to a 404 page. import { withAuth } from '@kinde-oss/kinde-auth-nextjs/server';
import { type NoseconeOptions, defaults, createMiddleware as noseconeMiddleware } from '@nosecone/next';
import createIntlMiddleware from 'next-intl/middleware';
import type { NextFetchEvent, NextMiddleware, NextRequest, NextResponse } from 'next/server';
import { match } from 'path-to-regexp';
import { routing } from './i18n/routing';
// Next.js middleware config
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] };
// Nosecone security headers configuration
// https://docs.arcjet.com/nosecone/quick-start
const noseconeOptions: NoseconeOptions = {
...defaults,
};
const securityHeaders = noseconeMiddleware(noseconeOptions);
const authMiddleware = async (request: NextRequest) => withAuth(request, { isReturnToCurrentPage: true });
const intlMiddleware = createIntlMiddleware(routing);
// Add any paths you want to run different middleware for. They use
// path-to-regexp which is the same as the Next.js config. You can provide a
// single middleware or an array of middlewares.
export default router({
// Run nosecone middleware on any path
'/{*path}': [securityHeaders, authMiddleware, intlMiddleware],
});
// Simplified version of nemo. This could be extracted into a utility library
function router(pathMiddlewareMap: Record<string, NextMiddleware | NextMiddleware[]>): NextMiddleware {
const middleware = Object.entries(pathMiddlewareMap).map(([path, middleware]) => {
if (Array.isArray(middleware)) {
return [match(path), middleware] as const;
}
return [match(path), [middleware]] as const;
});
return async (request: NextRequest, event: NextFetchEvent): Promise<NextResponse | Response> => {
const path = request.nextUrl.pathname || '/';
const addedHeaders = new Headers();
for (const [matchFunc, middlewareFuncs] of middleware) {
const m = matchFunc(path);
if (m) {
for (const fn of middlewareFuncs) {
const resp = await fn(request, event);
// TODO: better response guards
if (typeof resp !== 'undefined' && resp !== null) {
resp.headers.forEach((value, key) => {
addedHeaders.set(key, value);
});
}
}
}
}
addedHeaders.set('x-middleware-next', '1');
return new Response(null, {
headers: addedHeaders,
});
};
} This is my current implementation with Nemo that works but excluding the securityHeaders import { withAuth } from '@kinde-oss/kinde-auth-nextjs/middleware';
// import { type NoseconeOptions, defaults, createMiddleware as securityHeaders } from '@nosecone/next';
import { type MiddlewareFunction, type MiddlewareFunctionProps, createMiddleware } from '@rescale/nemo';
import createIntlMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
const intlMiddleware = createIntlMiddleware(routing);
// const noseconeOptions: NoseconeOptions = {
// ...defaults,
// };
// const before: MiddlewareFunction[] = [securityHeaders(noseconeOptions)];
const after: MiddlewareFunction[] = [
async ({ request }: MiddlewareFunctionProps) => {
return withAuth(request, { isReturnToCurrentPage: true });
},
async ({ request }: MiddlewareFunctionProps) => {
return intlMiddleware(request);
},
];
export const middleware = createMiddleware({}, { after });
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] }; |
I was able to reproduce this using next-intl. The issue comes from the custom router method ignoring a status code from other chained middleware. When loading the root path, next-intl will redirect to a locale e.g. Note that the downside of this implementation is that security headers won't be set on the redirecting page. This is an improvement that can be made later once we've tested this works for you: Here's the fixed implementation with next-intl installed. Drop the Kinde auth in and let me know how it goes: import { routing } from "@/i18n/routing";
import { type NoseconeOptions, defaults, createMiddleware as noseconeMiddleware } from "@nosecone/next";
import createMiddleware from 'next-intl/middleware';
import {
type NextFetchEvent,
type NextMiddleware,
type NextRequest,
NextResponse,
} from "next/server";
import { match } from "path-to-regexp";
// Next.js middleware config
export const config = {
matcher: ['/((?!_next/|_static|_vercel|[\\w-]+\\.\\w+).*)'],
};
// Nosecone security headers configuration
// https://docs.arcjet.com/nosecone/quick-start
const noseconeOptions: NoseconeOptions = {
...defaults,
};
const securityHeaders = noseconeMiddleware(noseconeOptions);
const intlMiddleware = createMiddleware(routing);
// Add any paths you want to run different middleware for. They use
// path-to-regexp which is the same as the Next.js config. You can provide a
// single middleware or an array of middlewares.
export default router({
// Run nosecone middleware on any path
"/{*path}": [securityHeaders, intlMiddleware],
});
// A simple middleware router that allows you to run different middleware based
// on the path of the request.
function router(
pathMiddlewareMap: Record<string, NextMiddleware | NextMiddleware[]>,
): NextMiddleware {
const middleware = Object.entries(pathMiddlewareMap).map(
([path, middleware]) => {
if (Array.isArray(middleware)) {
return [match(path), middleware] as const;
} else {
return [match(path), [middleware]] as const;
}
},
);
return async (
request: NextRequest,
event: NextFetchEvent,
): Promise<NextResponse | Response> => {
const path = request.nextUrl.pathname || "/";
const addedHeaders = new Headers();
for (const [matchFunc, middlewareFuncs] of middleware) {
const m = matchFunc(path);
if (m) {
for (const fn of middlewareFuncs) {
const resp = await fn(request, event);
// TODO: better response guards
if (typeof resp !== "undefined" && resp !== null) {
// If it's a redirect or auth status, bail immediately.
if (resp.status >= 300 && resp.status < 500) {
return resp;
}
resp.headers.forEach((value, key) => {
addedHeaders.set(key, value);
});
}
}
}
}
addedHeaders.set("x-middleware-next", "1");
return new Response(null, {
headers: addedHeaders,
});
};
} |
I am trying to implement |
@trevorpfiz it looks like the supabase |
@davidmytton Thanks! It seems to be working, but I will let you know if I run into any issues. |
I was wondering how I can combine the createMiddleware method with other middleware such as NextAuth
The text was updated successfully, but these errors were encountered: