Wali ahmed
Wali ahmed

Reputation: 91

Using next-auth & next-intl in middleware together?

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

Upvotes: 9

Views: 16807

Answers (7)

Y H R
Y H R

Reputation: 735

(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
}

Upvotes: 0

Alexander Savelyev
Alexander Savelyev

Reputation: 41

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

Upvotes: 3

Evert Arias
Evert Arias

Reputation: 61

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

Upvotes: 6

ARiyou Jahan
ARiyou Jahan

Reputation: 1069

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

Upvotes: 0

parikkap
parikkap

Reputation: 31

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

Upvotes: 1

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

Upvotes: 5

Luke Tsang
Luke Tsang

Reputation: 41

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]

Upvotes: 4

Related Questions