To be clear, I am encountering this issue only on production where after a user successfully logs in, selects products they want and add to cart, on clicking the checkout button which should take the user to the checkout page, it redirects the user back to the homepage and when you refresh the homepage, the user is seen as logged out. It is only supposed to redirect the user to the homepage if there is no session and it is confirmed that there is session but it redirects anyway and then logs the user out. This issue is quite frustrating as I have tried everything to have this fixed as it works perfectly in development. I have checked for production redirect uri on google console and it is correctly set, i have cleared cache, cookies, site data, etc. I have ran console logs in several components to try to catch where the error may be coming from in production but to no avail. So please help me look into this, you may find something I am missing. thanks
The following are the corresponding code to the issue
/cart.js
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import axios from "axios";
import { motion } from "framer-motion";
import Empty from "@/components/cart/empty";
import Header from "@/components/cart/header";
import CartProduct from "@/components/cart/product";
import styles from "@/styles/cart.module.scss";
import { updateCart } from "@/store/cartSlice";
import CartHeader from "@/components/cart/cartHeader.js";
import Checkout from "@/components/cart/checkout";
import PaymentMethods from "@/components/cart/paymentMethods.js";
import { women_swiper } from "@/data/home";
import { useSession, signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { saveCart } from "@/requests/user";
import Product from "@/models/Product";
import Category from "@/models/Category";
import db from "@/utils/db";
import Footer from "@/components/footer";
import ProductsSwiper from "@/components/productsSwiper";
export default function cart({ products, country }) {
const router = useRouter();
const { data: session } = useSession();
console.log("session", session)
const [selected, setSelected] = useState([]);
const { cart } = useSelector((state) => ({ ...state }));
const dispatch = useDispatch();
//-----------------------
const [shippingFee, setShippingFee] = useState(0);
const [subtotal, setSubtotal] = useState(0);
const [total, setTotal] = useState(0);
useEffect(() => {
setShippingFee(
selected.reduce((a, c) => a + Number(c.shipping), 0).toFixed(2)
);
setSubtotal(selected.reduce((a, c) => a + c.price * c.qty, 0).toFixed(2));
setTotal(
(
selected.reduce((a, c) => a + c.price * c.qty, 0) + Number(shippingFee)
).toFixed(2)
);
}, [selected]);
//-----------------------
const saveCartToDbHandler = async () => {
if (!session?.user) {
console.error("Session not established. Redirecting to sign-in...");
signIn();
return;
}
if (session) {
try {
console.log("Saving cart to database...", selected);
await saveCart(selected);
console.log("Cart saved successfully. Redirecting to /checkout...");
router.push("/checkout");
} catch (error) {
console.error("Error saving cart:", error);
}
} else {
console.log("User not logged in. Redirecting to sign-in...");
signIn();
}
};
return (
<>
<Header />
<div className={styles.cart}>
{cart.cartItems.length > 0 ? (
<div className={styles.cart__container}>
<CartHeader
cartItems={cart.cartItems}
selected={selected}
setSelected={setSelected}
/>
<div className={styles.cart__products}>
{cart.cartItems.map((product) => (
<CartProduct
product={product}
key={product._uid}
selected={selected}
setSelected={setSelected}
/>
))}
</div>
<Checkout
subtotal={subtotal}
shippingFee={shippingFee}
total={total}
selected={selected}
saveCartToDbHandler={saveCartToDbHandler}
/>
<PaymentMethods />
</div>
) : (
<Empty />
)}
</div>
{/* <ProductsSwiper products={products} /> */}
<Footer country={country} />
</>
);
}
export async function getServerSideProps() {
await db.connectDb();
let products = await Product.find()
.populate({ path: "category", model: Category })
.sort({ createdAt: -1 })
.lean()
.maxTimeMS(30000);
return {
props: {
products: JSON.parse(JSON.stringify(products)),
country: {
name: "Nigeria",
flag: "https://cdn.ipregistry.co/flags/emojitwo/ng.svg",
code: "NG",
},
},
};
}
/api/user/saveCart.js
import { createRouter } from "next-connect";
import Product from "@/models/Product";
import User from "@/models/user";
import Cart from "@/models/Cart";
import db from "@/utils/db";
import auth from "@/middleware/auth";
const router = createRouter().use(auth);
router.post(async (req, res) => {
try {
await db.connectDb();
const { cart } = req.body;
const user = await User.findById(req.user);
// Check if cart exists for the user, delete it if it does
let existingCart = await Cart.findOne({ user: user._id });
if (existingCart) {
await Cart.findByIdAndDelete(existingCart._id);
}
// Create a new cart
let products = [];
for (let i = 0; i < cart.length; i++) {
let dbProduct = await Product.findById(cart[i]._id).lean();
let subProduct = dbProduct.subProducts[cart[i].style];
let tempProduct = {
name: dbProduct.name,
product: dbProduct._id,
color: {
color: cart[i].color.color,
image: cart[i].color.image,
},
image: subProduct.images[0].url,
qty: Number(cart[i].qty),
size: cart[i].size,
};
let price = Number(
subProduct.sizes.find((p) => p.size == cart[i].size).price
);
tempProduct.price =
subProduct.discount > 0
? (price - price / Number(subProduct.discount)).toFixed(2)
: price.toFixed(2);
products.push(tempProduct);
}
let cartTotal = products.reduce(
(total, product) => total + product.price * product.qty,
0
);
await new Cart({
products,
cartTotal: cartTotal.toFixed(2),
user: user._id,
}).save();
await db.disconnectDb();
return res.status(200).json({ message: "Cart saved successfully" });
} catch (error) {
console.error("Error saving cart:", error);
await db.disconnectDb();
return res.status(500).json({ message: "Internal server error" });
}
});
router.post(async (req, res) => {
try {
await db.connectDb();
const { cart } = req.body;
const user = await User.findById(req.user);
// Check if cart exists for the user, delete it if it does
let existingCart = await Cart.findOne({ user: user._id });
if (existingCart) {
await Cart.findByIdAndDelete(existingCart._id);
}
// Create a new cart
let products = [];
for (let i = 0; i < cart.length; i++) {
let dbProduct = await Product.findById(cart[i]._id).lean();
let subProduct = dbProduct.subProducts[cart[i].style];
let tempProduct = {
name: dbProduct.name,
product: dbProduct._id,
color: {
color: cart[i].color.color,
image: cart[i].color.image,
},
image: subProduct.images[0].url,
qty: Number(cart[i].qty),
size: cart[i].size,
};
let price = Number(
subProduct.sizes.find((p) => p.size == cart[i].size).price
);
tempProduct.price =
subProduct.discount > 0
? (price - price / Number(subProduct.discount)).toFixed(2)
: price.toFixed(2);
products.push(tempProduct);
}
let cartTotal = products.reduce(
(total, product) => total + product.price * product.qty,
0
);
await new Cart({
products,
cartTotal: cartTotal.toFixed(2),
user: user._id,
}).save();
await db.disconnectDb();
return res.status(200).json({ message: "Cart saved successfully" });
} catch (error) {
console.error("Error saving cart:", error);
await db.disconnectDb();
return res.status(500).json({ message: "Internal server error" });
}
});
router.delete(async (req, res) => {
try {
await db.connectDb();
const { cart } = req.body;
const user = await User.findById(req.user);
// Check if cart exists for the user, delete it if it does
let existingCart = await Cart.findOne({ user: user._id });
if (existingCart) {
await Cart.findByIdAndDelete(existingCart._id);
}
await db.disconnectDb();
return res.status(200).json({ message: "Cart Deleted successfully" });
} catch (error) {
console.error("Error saving cart:", error);
await db.disconnectDb();
return res.status(500).json({ message: "Internal server error" });
}
});
export default router.handler();
/checkout.js
import { useState, useEffect } from "react";
import styles from "@/styles/checkout.module.scss";
import { getSession } from "next-auth/react";
import User from "@/models/user";
import Cart from "@/models/Cart";
import db from "@/utils/db";
import Header from "@/components/cart/header";
import Shipping from "@/components/checkout/shipping";
import Products from "@/components/checkout/products";
import Payment from "@/components/checkout/payment";
import Summary from "@/components/checkout/summary";
import { useDispatch } from "react-redux";
import { emptyCart } from "@/store/cartSlice";
export default function checkout({ cart, user }) {
const [addresses, setAddresses] = useState(user?.address || []);
const [paymentMethod, setPaymentMethod] = useState("");
const [totalAfterDiscount, setTotalAfterDiscount] = useState("");
const [selectedAddress, setSelectedAddress] = useState("");
const dispatch = useDispatch();
useEffect(() => {
let check = addresses?.find((ad) => ad.active == true);
if (check) {
setSelectedAddress(check);
} else {
setSelectedAddress("");
}
}, [addresses]);
return (
<>
<Header />
<div className={`${styles.container} ${styles.checkout}`}>
<div className={styles.checkout__side}>
<Shipping
user={user}
addresses={addresses}
setAddresses={setAddresses}
/>
<Products cart={cart} />
</div>
<div className={styles.checkout__side}>
<Payment
paymentMethod={paymentMethod}
setPaymentMethod={setPaymentMethod}
/>
<Summary
totalAfterDiscount={totalAfterDiscount}
setTotalAfterDiscount={setTotalAfterDiscount}
user={user}
cart={cart}
paymentMethod={paymentMethod}
selectedAddress={selectedAddress}
/>
</div>
</div>
</>
);
}
export async function getServerSideProps(context) {
await db.connectDb();
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: "/",
},
};
}
const user = await User.findById(session?.user?.id);
if (!user) {
return {
redirect: {
destination: "/signin",
},
};
}
const cart = await Cart.findOne({ user: user._id });
await db.disconnectDb();
if (!cart) {
return {
redirect: {
destination: "/cart",
},
};
}
return {
props: {
cart: JSON.parse(JSON.stringify(cart)),
user: JSON.parse(JSON.stringify(user)),
},
};
}
/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import Auth0Provider from "next-auth/providers/auth0";
import { MongoDBAdapter } from "@auth/mongodb-adapter";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcrypt";
import clientPromise from "./lib/mongodb";
import User from "@/models/user";
import db from "@/utils/db";
db.connectDb();
export default NextAuth({
adapter: MongoDBAdapter(clientPromise),
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const email = credentials.email;
const password = credentials.password;
const user = await User.findOne({ email });
if (user) {
return SignInUser({ password, user });
} else {
throw new Error("This email does not exist");
}
},
}),
Auth0Provider({
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
issuer: process.env.AUTH0_ISSUER,
}),
],
callbacks: {
async session({ session, token }) {
let user = await User.findById(token.sub);
session.user.id = token.sub || user._id.toString();
session.user.role = user.role || "user";
token.role = user.role || "user";
return session;
},
},
pages: {
signIn: "/signin",
},
session: {
strategy: "jwt",
},
secret: process.env.JWT_SECRET,
});
const SignInUser = async ({ password, user }) => {
if (!user.password) {
throw new Error("Please enter your password");
}
const testPassword = await bcrypt.compare(password, user.password);
if (!testPassword) {
throw new Error("Email or password is wrong!");
}
return user;
};
/requests/user.js
import axios from "axios";
export const saveCart = async (cart) => {
try {
const { data } = await axios.post("/api/user/saveCart", {
cart,
});
console.log("API Response:", data);
return data;
} catch (error) {
console.error("API Error:", error.response?.data || error.message);
if (error.response?.status === 500) {
alert("Server error occurred while saving the cart. Please try again.");
}
throw error
}
};
This is a screenshot of my log on vercel when i try to head to the checkout page
so basically, getSession from next-auth/react is no more being accepted when trying to fetch session in the server side next-auth.js.org/configuration/nextjs
getServerSession from next-auth is the best way to get the session through context and passing props which includes authOptions function from the [...nextauth].js below are the modifications are made in two files [...nextauth].js and checkout.js
pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import TwitterProvider from "next-auth/providers/twitter";
import FacebookProvider from "next-auth/providers/facebook";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
import Auth0Provider from "next-auth/providers/auth0";
import EmailProvider from "next-auth/providers/email";
import { MongoDBAdapter } from "@auth/mongodb-adapter";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcrypt";
import clientPromise from "./lib/mongodb";
import User from "@/models/user";
import db from "@/utils/db";
db.connectDb();
export const authOptions = {
adapter: MongoDBAdapter(clientPromise),
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const email = credentials.email;
const password = credentials.password;
const user = await User.findOne({ email });
if (user) {
return SignInUser({ password, user });
} else {
throw new Error("This email does not exist");
}
},
}),
Auth0Provider({
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
issuer: process.env.AUTH0_ISSUER,
}),
],
callbacks: {
async session({ session, token }) {
let user = await User.findById(token.sub);
session.user.id = token.sub || user._id.toString();
session.user.role = user.role || "user";
token.role = user.role || "user";
return session;
},
},
pages: {
signIn: "/signin",
},
session: {
strategy: "jwt",
},
secret: process.env.NEXTAUTH_SECRET,
};
export default NextAuth(authOptions)
Instead of exporting NextAuth directly, creating a function called authOptions, writing all the logic and credentials in it and passing it as props in the NextAuth
/pages/checkout.js
import { useState, useEffect } from "react";
import styles from "@/styles/checkout.module.scss";
import User from "@/models/user";
import Cart from "@/models/Cart";
import db from "@/utils/db";
import Header from "@/components/cart/header";
import Shipping from "@/components/checkout/shipping";
import Products from "@/components/checkout/products";
import Payment from "@/components/checkout/payment";
import Summary from "@/components/checkout/summary";
import { useDispatch } from "react-redux";
import { getSession } from "next-auth/react";
import { authOptions } from "./api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
export default function checkout({ cart, user }) {
const [addresses, setAddresses] = useState(user?.address || []);
const [paymentMethod, setPaymentMethod] = useState("");
const [totalAfterDiscount, setTotalAfterDiscount] = useState("");
const [selectedAddress, setSelectedAddress] = useState("");
const dispatch = useDispatch();
useEffect(() => {
let check = addresses?.find((ad) => ad.active == true);
if (check) {
setSelectedAddress(check);
} else {
setSelectedAddress("");
}
}, [addresses]);
return (
<>
<Header />
<div className={`${styles.container} ${styles.checkout}`}>
<div className={styles.checkout__side}>
<Shipping
user={user}
addresses={addresses}
setAddresses={setAddresses}
/>
<Products cart={cart} />
</div>
<div className={styles.checkout__side}>
<Payment
paymentMethod={paymentMethod}
setPaymentMethod={setPaymentMethod}
/>
<Summary
totalAfterDiscount={totalAfterDiscount}
setTotalAfterDiscount={setTotalAfterDiscount}
user={user}
cart={cart}
paymentMethod={paymentMethod}
selectedAddress={selectedAddress}
/>
</div>
</div>
</>
);
}
export async function getServerSideProps(context) {
try{
await db.connectDb();
const session = await getServerSession(context.req, context.res, authOptions);
if (!session) {
return {
redirect: {
destination: "/",
},
};
}
const user = await User.findById(session?.user?.id);
if (!user) {
return {
redirect: {
destination: "/signin",
},
};
}
const cart = await Cart.findOne({ user: user._id });
await db.disconnectDb();
if (!cart) {
return {
redirect: {
destination: "/cart",
},
};
}
return {
props: {
cart: JSON.parse(JSON.stringify(cart)),
user: JSON.parse(JSON.stringify(user)),
},
};
}catch (error){
return {
redirect: {
destination: "/network-error",
permanent: false,
},
};
}
}
calling import { authOptions } from "./api/auth/[...nextauth]";
from the nextauth file and replacing import {getSession} from "next-auth/react";
with
import { getServerSession } from "next-auth";
which is then called in the getServerProps above fixed it.