node.jstypescriptnext.jsnext-auth

Next js authentication for API calls does not work


I've created an app successfully using the nextjs learn (the app router way) - specifically the "Adding Authentication" part.

Authentication works fine.
Now, I wanted to add an api route.
So I've created a file route.ts under app/api/my-api/ with the content:

import { NextResponse } from "next/server";

export async function POST(req: Request) {
  return NextResponse.json({ message: "hello" }, { status: 200 });
}

I removed the api part from the matcher of middleware.ts's export const config. Meaning, authentication should apply to /api/... as well:

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

export default NextAuth(authConfig).auth;

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

I would expect the following command to fail due to no authentication, but it passes:

curl -v -X POST 'http://localhost:3000/api/my-api'
...
< HTTP/1.1 200 OK

As in the example, auth.ts is

import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { sql } from '@vercel/postgres';
import { z } from 'zod';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
 
// ...
 
export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(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;
      },
    }),
  ],
});

And auth.config.ts is:

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;

Solution

  • So, looks like there are 2 different things here:

    Aparently they cannot be mixed.
    I had to do there things for making authentication using next-auth to work for api in addition to the tutorial:

    1. In auth.config.ts, the last return true should be return false, so api calls will not be authenticated.
    2. In auth.config.ts, change the function signature to authorized({ request, auth }), so I can use what's in request. I somehow learned it from here
    3. Now I can have in auth.config.ts's authorized() my own logic. For example:
    if (request.nextUrl.pathname.startsWith('/api')) {
      ...
    }