I am using supabase to implement MFA in my auth but always encounter “Missing Sub Claim error”
I use custom claims during signup to store mfa status and factor ID during mfa enroll during signup so user object becomes
First the user has to sign In successfully to reach the otp page and a session cookie is successfully set in my browser
Then I use this code from backend
//auth.js
export const mfaVerify = async (fId, otp) => {
try {
const { data, error } = await supabase.auth.mfa.challengeAndVerify({
factorId: fId,
code: otp,
});
if (error) {
console.log("err is", error);
throw new Error(`MFA Verification failed: ${error.message}`);
}
if (!data || !data.access_token) {
console.log("NO data");
throw new Error("MFA Verification failed: Missing access token or data");
}
return { data, error: null };
} catch (err) {
console.log("MFA Verification Error:", err);
return { data: null, error: err };
}
};
//authRouter.js
authRouter.post("/mfaVerify", async (req, res, next) => {
try {
const { otp, mfaID } = req.body;
console.log("OTP is", otp);
console.log("id is", mfaID);
// const { error } = otpValid({ otp, mfaID });
// const clientType = req.headers["client-type"];
// if (error)
// return res
// .status(400)
// .json({ status: "false", message: "Incorrect OTP entered" });
const resp = await mfaVerify(mfaID, otp);
console.log("resp is", resp);
if (resp.error)
return res
.status(401)
.json({ status: false, message: "Incorrect OTP entered" });
else {
if (clientType === "react-native") {
res.status(200).json({
access_token: resp.data.access_token,
refresh_token: resp.data.refresh_token,
expires_in: resp.data.expires_in,
});
} else {
res
.cookie("access", resp.data.access_token, {
httpOnly: true, // Ensures the cookie is only accessible via HTTP(S), not JavaScript
secure: true, // Ensures the cookie is only sent over HTTPS
maxAge: resp.data.expires_in * 1000, // Sets the cookie expiration time
sameSite: "lax",
partitioned: true,
signed: true,
})
.cookie("refresh", resp.data.refresh_token, {
httpOnly: true, // Accessible only by the web server
secure: true, // Transmit cookie only over HTTPS
maxAge: 30 * 24 * 60 * 60 * 1000, // Refresh token typically has a longer lifespan (e.g., 30 days)
sameSite: "lax", // Prevents CSRF attacks
partitioned: true, // Ensures cookie isolation
signed: true, // Prevents tampering with the cookie
})
.status(200)
.json({
status: true,
message: "Sign in successfull",
adminDat: !!resp.data.user.app_metadata.claims_admin,
});
}
}
} catch (err) {
console.log(err);
return res.status(500).json("Internal Server Error");
}
});
And this code is in frontend
//Auth.ts
export const verifyMyOTP = async (otp: string, mfaID: string, router: any) => {
try {
const resp = await axios.post(
`${String(import.meta.env["VITE_BASE_URL"])}/auth/mfaVerify`,
{ otp, mfaID },
{
withCredentials: true,
}
);
if (resp.data && resp.data.status) {
console.log(resp.data);
const role = resp.data.adminDat ? "admin" : "user";
router.navigate({ to: `/${role}/Home` });
return { status: true, message: "OTP CONFIRMED" };
} else {
console.log(resp.data);
return { status: false, message: "OTP INVALID" };
}
} catch (error) {
if (error.response && error.response.status === 400) {
console.log("Invalid code");
return { status: false, message: "Invalid Code" };
}
}
};
//AuthCard.tsx
const submitOTP = async () => {
if (!/^d{6}$/.test(mfaData.otp)) return message.error("Invalid OTP");
try {
const res = await verifyMyOTP(mfaData.otp, mfaData.mfaID, router);
if (res?.status) return message.success(res.message);
} catch (error) {
console.log(error);
message.error("error");
}
};
enter code here
<div className="flex items-center justify-center mt-5 flex-col">
<div className="flex flex-col items-center justify-center text-center space-y-2">
<div>
<h2 className="font-semibold text-3xl">MFA</h2>
</div>
<div className="flex flex-row text-sm font-medium text-muted">
<p>
Enter the time sensitive 6 digit code from your
authenticator app
</p>
</div>
</div>
<div className="mt-5 m-7">
<Input.OTP length={6} {...sharedProps} />
</div>
<button
onClick={async () => {
await submitOTP();
}}
disabled={mfaData.otp.length === 6 ? false : true}
className="inline-block w-full sm:w-[282px] ml-4 mr-4 rounded bg-blue-400 px-5 py-3 font-medium text-white shadow-md shadow-blue-500/20 hover:bg-blue-500 hover:text-white"
>
Sign in
</button>
<p className="text-center text-gray-500 mt-4">
Need help? Please contact Support.
</p>
</div>
Can someone please help me solve my problem or give me some debugging tips to solve this problem