Next-Auth authentication to Django Rest Framework with JWT and App Router

I have a Django project, for which I’ve decided to add Next.JS frontend. I set-up DRF authentication with JWT:

$ curl -XPOST -H "Content-type: application/json" -d '{
      "username": "user1",
      "password": "strongpassword"
  }' 'http://0.0.0.0:8000/api/auth/login/'
{"access":"eyJhbG...","refresh":"eyJhbG...","user":{"pk":1,"username":"user1","email":"","first_name":"John","last_name":"Doe"}}

I’ve configured Next-Auth in my frontend:

// frontend/auth.ts

import type {
  GetServerSidePropsContext,
  NextApiRequest,
  NextApiResponse,
} from "next";
import type { NextAuthOptions } from "next-auth";
import { getServerSession } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";

const BACKEND_ACCESS_TOKEN_LIFETIME = 45 * 60; // 45 minutes
const BACKEND_REFRESH_TOKEN_LIFETIME = 6 * 24 * 60 * 60; // 6 days

const getCurrentEpochTime = () => {
  return Math.floor(new Date().getTime() / 1000);
};

export const authOptions = {
  pages: {
    signIn: "/login",
  },
  secret: process.env.AUTH_SECRET,
  session: {
    strategy: "jwt",
    maxAge: BACKEND_REFRESH_TOKEN_LIFETIME,
  },
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {},
      async authorize(credentials, req) {
        try {
          const response = await axios({
            url: process.env.NEXTAUTH_BACKEND_URL + "auth/login/",
            method: "post",
            data: credentials,
          });
          const data = response.data;
          if (data) return data;
        } catch (error) {
          console.error(error);
        }
        return null;
      },
    }),
  ],
  callbacks: {
    async jwt({ user, token, account }) {
      // If `user` and `account` are set that means it is a login event
      if (user && account) {
        let backendResponse: any =
          account.provider === "credentials" ? user : account.meta;
        token["user"] = backendResponse.user;
        token["access_token"] = backendResponse.access;
        token["refresh_token"] = backendResponse.refresh;
        token["ref"] = getCurrentEpochTime() + BACKEND_ACCESS_TOKEN_LIFETIME;
        return token;
      }
      // Refresh the backend token if necessary
      if (getCurrentEpochTime() > token["ref"]) {
        const response = await axios({
          method: "post",
          url: process.env.NEXTAUTH_BACKEND_URL + "auth/token/refresh/",
          data: {
            refresh: token["refresh_token"],
          },
        });
        token["access_token"] = response.data.access;
        token["refresh_token"] = response.data.refresh;
        token["ref"] = getCurrentEpochTime() + BACKEND_ACCESS_TOKEN_LIFETIME;
      }
      return token;
    },

    async session({ token }) {
      return token;
    },
  },
} satisfies NextAuthOptions;

export function auth(
  ...args:
    | [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]]
    | [NextApiRequest, NextApiResponse]
    | []
) {
  return getServerSession(...args, authOptions);
}

Added routes:

//  frontend/app/api/auth/[...nextauth]/route.ts

import NextAuth from "next-auth";
import { authOptions } from "@/auth";
const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

And middleware:

// frontend/middleware.ts

import { withAuth } from "next-auth/middleware";

export default withAuth(
  function middleware(req) {
    console.log(req.nextauth.token);
  },
  {
    callbacks: {
      authorized: ({ token }) => !!token,
    },
  }
);

export const config = { matcher: ["/dashboard/:path*"] };

Created a login form:

// frontend/app/ui/login-form.tsx

"use client";

import { useFormState, useFormStatus } from "react-dom";
import { authenticate } from "../lib/actions";

export default function LoginForm() {
  const [errorMessage, dispatch] = useFormState(authenticate, undefined);

  return (
    <form action={dispatch}>
      <input              
        id="username"
        type="text"
        name="username"
        placeholder="Username"
        required
      />
      <input               
          id="password"
          type="password"
          name="password"
          placeholder="Enter password"
          required
          minLength={6}
        /> 
      <LoginButton />  
    </form>
  );
}

function LoginButton() {
  const { pending } = useFormStatus();

  return (
    <Button aria-disabled={pending}>
      Log in
    </Button>
  );
}

And defined authenticate function to access /api/auth/signin endpoint:

// frontend/app/lib/actions.ts

export async function authenticate(
  prevState: string | undefined,
  formData: FormData
) {
  try {
    const res = await fetch(`/api/auth/signin`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        username: formData.get("username"),
        password: formData.get("password"),
      }),
    });
  } catch (error) {
    if (error) {
      return "Something went wrong.";
    }
    throw error;
  }
}

But when I click login button, nothing happens. Am I using the app router the wrong way?