9

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

0

7 Answers 7

6

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);
Sign up to request clarification or add additional context in comments.

Comments

5

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|.*\\..*).*)']
};

Comments

4

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]

1 Comment

Only public pages have Internationalization. The way I see it
3

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

1 Comment

This was extremely helpful and simple, solved my issue without any serious complexity. Thank you!
1

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);
});

Comments

0

Let nextAuth handle it first and then pass that request to I18nMiddleware

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).*)",
  ],
};

Comments

0

(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

  • first, via middleware's function, if user isn't trying to access protected routes;

or

  • second, via authMiddleware after it has completed its job.

My code covers 3 typical usecases:

  • If authenticated user tries to access /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
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.