ERR_TOO_MANY_REDIRECTS on composing next-intl and next-auth middleware

1.4k Views Asked by At

Disclaimer: I am using the code in https://github.com/amannn/next-intl/tree/main/examples/example-next-13-next-auth as base point for my implementation.

I am failing to understand why composing next-intl and next-auth results in an infinite loop. I am basing my code on the above example (which I failed to run, tbh, but hope is solid). I had to make a couple of changes, due to how the project's business logic flow:

  1. You do not have unprotected pages
  2. You have to be redirected to the auth provider's page the moment you land on the home page
  3. You need to be logged out via the auth provider

To do so, I have created the following folder structure: enter image description here

This is my middleware:

import { NextRequestWithAuth, withAuth } from 'next-auth/middleware';
import createIntlMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';

const locales = ['bg', 'en'];

const intlMiddleware = createIntlMiddleware({
  locales,
  defaultLocale: 'bg',
});

const authMiddleware = withAuth(
  (req: NextRequestWithAuth) => {
    const now: any = new Date();
    const timestamp = Math.round(now / 1000);
    const { expiresAt }: any = req.nextauth.token;

    if (timestamp > expiresAt) {
      if (!req.nextUrl.pathname.startsWith('/auth')) {
        const url = '/auth/signout';
        return NextResponse.redirect(new URL(url, req.nextUrl));
      }
    }

    // THIS IS WHERE THE MULTIPLE REDIRECTS HAPPEN
    return intlMiddleware(req);
  },
  {
    pages: {
      signIn: '/auth/signin',
      error: '/auth/error',
      signOut: '/auth/signout',
    },
  },
);

export default function middleware(req: NextRequest) {
  return (authMiddleware as any)(req);
}

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)'],
};

This is my app/layout:

'use client';

import React from 'react';
import { SessionProvider } from 'next-auth/react';
import { BodyStyled } from '@/global/styles';

type RootLayoutTypes = {
  children: React.ReactNode,
  params: any
}

export default function RootLayout({ children, params: { locale } }: RootLayoutTypes) {
  return (
    <html lang={locale}>
      <BodyStyled>
        <SessionProvider>
          {children}
        </SessionProvider>
      </BodyStyled>
    </html>
  );
}

this is my app/[locale]/layout:

import React from 'react';
import { NextIntlClientProvider } from 'next-intl';
import bg from '@/public/locales/bg/common.json';
import en from '@/public/locales/en/common.json';

type RootLayoutTypes = {
  children: React.ReactNode,
  params: any
}

export function generateStaticParams() {
  return [{ locale: 'bg' }, { locale: 'en' }];
}

export default function RootLayout({ children, params: { locale } }: RootLayoutTypes) {
  return (
    <NextIntlClientProvider locale={locale} messages={locale === 'bg' ? bg : en}>
      {children}
    </NextIntlClientProvider>
  );
}

This is my app/api/auth/[...nextauth]/route code:

/* eslint-disable no-param-reassign */
import NextAuth, { NextAuthOptions } from 'next-auth';
import jwtDecode from 'jwt-decode';

const authOptions: NextAuthOptions = {
  providers: [
    {
      wellKnown: `${process.env.NEXT_PUBLIC_AUTH_AUTHORITY}/.well-known/openid-configuration`,
      authorization: {
        params: { scope: 'openid profile' },
      },
      type: 'oauth',
      id: 'customOauth',
      clientId: process.env.NEXT_PUBLIC_AUTH_CLIENT_ID,
      clientSecret: process.env.NEXT_PUBLIC_AUTH_CLIENT_SECRET,
      name: '',
      checks: ['pkce', 'state'],
      profile(profile) {
        return {
          id: profile.sub,
          permissions: profile.permissions,
        };
      },
    },
  ],
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: 'jwt',
  },
  callbacks: {
    async redirect() {
      return '/dashboard';
    },
    async jwt({ token, account }) {
      // Persist the OAuth access_token and or the user id to the token right after signin
      if (account) {
        const { exp }: any = jwtDecode(account.id_token || '');
        token.accessToken = account.access_token;
        token.idToken = account.id_token;
        token.expiresAt = exp;
      }
      return token;
    },
  },
  pages: {
    signIn: '/auth/signin',
  },
  debug: true,
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST }

and this is the app/auth/signin page:

'use client';

import React, { useEffect } from 'react';
import { signIn, useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';

interface LoginProps {
}

const Login: React.FC<LoginProps> = () => {
  const router = useRouter();
  const { status } = useSession();

  useEffect(() => {
    if (status === 'unauthenticated') {
      const queryString = window.location.search;
      const urlParams = new URLSearchParams(queryString);
      if (urlParams.get('error')) {
        void router.push('/auth/error');
      } else {
        void signIn('customOauth', undefined, { prompt: 'select_account' });
      }
    } else if (status === 'authenticated') {
      void router.push('/dashboard');
    }
  }, [status, router]);

  return null;
};

export default Login;

What am I missing? Any help will be greatly appreciated. Been on this s**t for 3 weeks now, i would love to move on...

2

There are 2 best solutions below

0
On BEST ANSWER

Turns out I had this configuration in next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
/** error: */
  i18n: {
    locales: ['bg', 'en'],
    defaultLocale: 'bg',
  },
/** end error */
  async redirects() {
    return [
      {
        source: '/',
        destination: '/dashboard',
        permanent: true,
      },
    ];
  },
};

module.exports = nextConfig;

You should NOT specify i18n when using next-intl. This gave the redirect error.

1
On
 import { withAuth } from "next-auth/middleware";export default 


  withAuth({ pages: { signIn: "/auth/signin",},});