Skip to content
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

Open
JulianJorgensen opened this issue Nov 3, 2023 · 10 comments
Open

Support for NextAuth v5 multiple middleware #596

JulianJorgensen opened this issue Nov 3, 2023 · 10 comments
Labels
area: integrations contributions welcome Good for people looking to contribute enhancement New feature or request

Comments

@JulianJorgensen
Copy link

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 using auth.

Would love to see.

Describe the solution you'd like

I imagine the usage something like this:

import { auth } from 'auth';
import createMiddleware from 'next-intl/middleware';

const intlMiddleware = createMiddleware({
  locales: ['en', 'dk'],
  defaultLocale: 'en'
});

export default auth((req) => intlMiddleware(req))

Describe alternatives you've considered

Alternatively I can stick with using next-auth v4

@JulianJorgensen JulianJorgensen added enhancement New feature or request unconfirmed Needs triage. labels Nov 3, 2023
@amannn
Copy link
Owner

amannn commented Nov 3, 2023

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 main branch.

@JulianJorgensen
Copy link
Author

Sure thing! I just made a PR, but my PR skills are a bit rusty so bear with me 🙂

@amarincolas
Copy link

amarincolas commented Nov 7, 2023

I am trying to make it work but apparently, this does not work anymore

const authMiddleware = auth(
  (req) => intlMiddleware(req),
  {
    callbacks: {
      authorized: ({token}) => token != null
    },
    pages: {
      signIn: '/signin'
    }
  }
);

So I decided to follow the next-auth and next.js guidelines about this and I came up with this:

export const authConfig = {
  pages: {
    signIn: '/signin',
  },
  providers: [],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      ...
    },
  },
} satisfies NextAuthConfig
export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        username: { type: 'text' },
        password: { type: 'password' },
      },
      authorize(credentials) {
        ...
    }),
  ],
})
const authMiddleware = auth(
  (request) => intlMiddleware(request)
)

But the thing is this does not work either...

@benjamintalou
Copy link

benjamintalou commented Nov 17, 2023

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';

@juancmandev
Copy link

juancmandev commented Nov 20, 2023

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*'],
};

@amannn amannn added contributions welcome Good for people looking to contribute and removed unconfirmed Needs triage. labels Nov 23, 2023
@Aw3same
Copy link

Aw3same commented Nov 28, 2023

I am trying to make it work but apparently, this does not work anymore

const authMiddleware = auth(
  (req) => intlMiddleware(req),
  {
    callbacks: {
      authorized: ({token}) => token != null
    },
    pages: {
      signIn: '/signin'
    }
  }
);

So I decided to follow the next-auth and next.js guidelines about this and I came up with this:

export const authConfig = {
  pages: {
    signIn: '/signin',
  },
  providers: [],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      ...
    },
  },
} satisfies NextAuthConfig
export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        username: { type: 'text' },
        password: { type: 'password' },
      },
      authorize(credentials) {
        ...
    }),
  ],
})
const authMiddleware = auth(
  (request) => intlMiddleware(request)
)

But the thing is this does not work either...

Any update on this? I'm at the same point, getting this error:

image

And in the server:

⨯ node_modules\next-intl\dist\development\middleware\middleware.js (31:71) @ get
⨯ Cannot read properties of undefined (reading 'get')

The concrete line is this, if it helps
const hasOutdatedCookie = ((_request$cookies$get = request.cookies.get(constants.COOKIE_LOCALE_NAME)) === null || _request$cookies$get === void 0 ? void 0 : _request$cookies$get.value) !== locale;

UPDATE:

It seems to be a problem with the auth wrapper, because is missing the cookies see more

@alessandrojcm
Copy link

alessandrojcm commented Dec 15, 2023

What worked for me was to use the auth() overload like here and return the intl response. Like so:

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.

@wallwhite
Copy link

wallwhite commented Jan 7, 2024

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.

@0x963D
Copy link

0x963D commented Feb 26, 2024

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

@kisstamasj
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: integrations contributions welcome Good for people looking to contribute enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

11 participants