0

I’m working on a Next.js 14 app with the App Router and next-auth@5. I followed the official Next.js Learn guide on authentication, except I replaced email with username in the database.

I set up middleware.ts, auth.config.ts, and auth.ts (merged the last two into one file).

However, the middleware never triggers — I placed console.log statements inside it and none run. There’s no error in the browser or the server, just no effect.

I suspect I’m missing a required export or config somewhere, but I can’t pinpoint it.


Here’s a simplified version of my setup:

// middleware.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export default NextAuth(authConfig).auth;

export const config = {
  // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
// auth.ts
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import NextAuth from 'next-auth';
import sql from "@/app/lib/data";
import bcrypt from 'bcrypt';
import { z } from 'zod';

async function getUser(email: string) {
  try {
    const user = await sql`SELECT * FROM users WHERE username=${email}`;
    return user[0];
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw new Error('Failed to fetch user.');
  }
}

export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(credentials) {
        const parsedCredentials = z
          .object({ email: z.string().email(), password: z.string().min(6) })
          .safeParse(credentials);

        if (parsedCredentials.success) {
          const { email, password } = parsedCredentials.data;
          const user = await getUser(email);
          if (!user) return null;
          const passwordsMatch = await bcrypt.compare(password, user.password);

          if (passwordsMatch) return user;
        }

        console.log('Invalid credentials');
        return null;
      },
    }),
  ],
});
//auth.config.ts
import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
  pages: {
    signIn: '/login',
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      if (isOnDashboard) {
        if (isLoggedIn) return true;
        return false; // Redirect unauthenticated users to login page
      } else if (isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl));
      }
      return true;
    },
  },
  providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
// /src/app/dashboard/page.tsx
export default function Page() {
    return (
        <>
            <p>Dashboard page</p>
        </>
    )
}
// /src/app/login/page.tsx
"use client";

import { useActionState } from "react";
import { authenticate } from "@/app/lib/actions";
import { useSearchParams } from "next/navigation";

export default function LoginForm() {
  const searchParams = useSearchParams();
  const callbackUrl = searchParams.get("callbackUrl") || "/dashboard";
  const [errorMessage, formAction, isPending] = useActionState(
    authenticate,
    undefined,
  );

  return (
    <form action={formAction}>
      <label htmlFor="email">Email</label>

      <input
        id="email"
        type="email"
        name="email"
        placeholder="Enter your email address"
        required
      />

      <label htmlFor="password">Password</label>

      <input
        id="password"
        type="password"
        name="password"
        placeholder="Enter password"
        required
        minLength={6}
      />

      <input type="hidden" name="redirectTo" value={callbackUrl} />
      <button aria-disabled={isPending}>Log in</button>
      {errorMessage && (
        <>
          <p>{errorMessage}</p>
        </>
      )}
    </form>
  );
}

2 Answers 2

1

resolved:
change location root/middleware.ts -> src/middleware.ts

Sign up to request clarification or add additional context in comments.

Comments

1

Your middleware doesnt trigger cause your matcher config is wrong:

matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],

the ! before api means exclude the middleware from the following routes. I dont know your whole setup, but i think you want authentication in your api routes, so you can remove the 'api' from there. This doesnt mean you have to be authenticated to access an api route, you still need to implement that yourself, but the session gets refreshed.
matcher: ['/((?!_next/static|_next/image|.*\\.png$).*)'],

Also you may want to checkout authjs. its basicly a newer version of nextauth and works great. I dont know what it was exactly but it made auth in api routes a bit easier.

Edit: Paste your matcher config into chatGPT, it can explain it to you so understand the cryptic signs.

3 Comments

It didn't work but I finally resolved the bug
How do you resolve the bug ?
Sorry but I edited too many files to write it down here. Maybe check my fixed code? Hope it will help you Ps: good luck understanding it, I didn't commented anything ^^

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.