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?