i have an API login that receive 2 param: usernameOrEmail and password, after fetch success it will return 3 param: accessToken, refreshToken and tokenType. Then i have another API that receive accessToken and return user information, but when i tried to login the server return https://next-auth.js.org/errors#jwt_session_error Cannot read properties of undefined (reading 'accessToken')
. The API is successfully run in Postman
This is file ‘api/auth/[…nextauth.js]’:
import NextAuth from "next-auth/next";
import GoogleProvider from 'next-auth/providers/google'
import CredentialsProvider from "next-auth/providers/credentials";
import axios from 'axios'
import { API_BASE_URL } from "@/config/apiConfig";
export default NextAuth({
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
usernameOrEmail: { label: 'Username or Email', type: 'text' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
try {
const response = await axios.post(`${API_BASE_URL}api/auth/login`, {
usernameOrEmail: credentials.usernameOrEmail,
password: credentials.password,
}, {
headers: {
accept: '*/*',
'Content-Type': 'application/json'
}
});
const { accessToken, refreshToken, tokenType } = response.data;
if (accessToken) {
return {
accessToken,
refreshToken,
tokenType,
}
}
return null;
} catch (error) {
console.log(error.response?.data?.message || 'Login failed');
}
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
redirect_uri: `oauth2/authorize/google?redirect_uri=http://localhost:3000/oauth2/redirect`
}
}
}),
],
pages: {
signIn: '/login',
},
session: {
jwt: true
},
callbacks: {
async jwt(token, user) {
if (user) {
token.accessToken = user.accessToken;
token.refreshToken = user.refreshToken;
token.tokenType = user.tokenType;
}
return token
},
async session(session, token) {
if (token.accessToken) {
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken;
session.tokenType = token.tokenType;
try {
const userResponse = await axios.get(`${API_BASE_URL}api/auth/profile`, {
headers: {
Authorization: `Bearer ${token.accessToken}`,
}
});
session.user = userResponse.data;
} catch (error) {
console.error("Failed to fetch user:", error.response?.data || error.message);
}
}
return session
}
},
secret: process.env.NEXTAUTH_SECRET,
})
This is file FormLogin.js:
import React, { useState } from "react";
import Button from "@mui/material/Button";
import Link from "next/link";
import { CustomTextField } from "./CustomTextField";
import { useRouter } from "next/router";
import { signIn } from "next-auth/react";
import "react-toastify/dist/ReactToastify.css";
const FormLogin = () => {
const router = useRouter();
const [formData, setFormData] = useState({
usernameOrEmail: "",
password: "",
});
const [error, setError] = useState({
usernameOrEmail: "",
password: "",
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const validateForm = () => {
const newErrors = {};
if (formData.usernameOrEmail.trim() === "") {
newErrors.usernameOrEmail = "Email không được để trống";
}
if (formData.password.trim() === "") {
newErrors.password = "Password không được để trống";
}
return newErrors;
};
const handleSubmit = async (e) => {
e.preventDefault();
const newErrors = validateForm();
if (Object.keys(newErrors).length > 0) {
setError(newErrors);
return;
}
setError({});
const result = await signIn('credentials', {
redirect: false,
usernameOrEmail: formData.usernameOrEmail,
password: formData.password,
})
if (result.error) {
setError({ form: result.error })
} else {
router.push('/product')
}
};
const handleGoogleLogin = () => {
signIn('google')
}
return (
<div className="w-full py-[5rem]">
<div className="w-2/5 p-4 m-auto bg-white rounded-2xl">
<p className="mb-5 text-2xl font-bold text-center text-orange-gray text">
Login
</p>
<form className="px-4">
<CustomTextField
className="mb-4"
label="Tên đăng nhập hoặc Email"
name="usernameOrEmail"
value={formData.usernameOrEmail}
onChange={handleInputChange}
fullWidth
error={Boolean(error.usernameOrEmail)}
helperText={error.usernameOrEmail}
/>
<CustomTextField
className="mb-4"
label="Mật khẩu"
type="password"
name="password"
value={formData.password}
onChange={handleInputChange}
fullWidth
margin="normal"
error={Boolean(error.password)}
helperText={error.password}
/>
<Link href={"#"}>
<p className="text-base text-orange-gray hover:opacity-80">
Quên mật khẩu?
</p>
</Link>
<div className="flex justify-center gap-5 mt-4 ">
<Button
className="text-base font-semibold bg-light-brown text-orange-gray rounded-2xl hover:bg-light-brown hover:bg-opacity-80"
type="submit"
variant="contained"
onClick={handleSubmit}
size="large"
>
Đăng nhập
</Button>
<Button
className="text-base font-semibold bg-light-brown text-orange-gray rounded-2xl hover:bg-light-brown hover:bg-opacity-80"
type="submit"
variant="contained"
color="primary"
size="large"
onClick={(e) => {
e.preventDefault();
router.push("/signup");
}}
>
Đăng ký
</Button>
</div>
</form>
</div>
</div>
);
};
export default FormLogin;
For example, the API should return like this:
{
“accessToken”: “eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiIxMjM0IiwiaWF0IjoxNzE2NzEzMjk5LCJleHAiOjE3MTY3MzQ3Mzl9.fxLO-c-w7RuCUigYzYLgeu1138icfDPZT0MuWgEby4XpJXq95fAfowl0hPjzV-DB”,
“refreshToken”: “eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiIxMjM0IiwiaWF0IjoxNzE2NzEzMjk5LCJleHAiOjE3MTczMTgwOTl9.bEu-dYiD6eENiKQdIMPQKKl4PF7-c-u3hPrgiad_0ZJCxi5aPaYVVOt79okP9BMc”,
“tokenType”: “Bearer”
}