Closed loyoliteabid closed 1 month ago
did you try it with type: 'email'
instead of 'email_change'. For what exactly is the type 'email_change' used for?
I cannot find any explanation about this type in the docs.
email_change is mentioned here https://supabase.com/docs/reference/javascript/auth-verifyotp , you can reproduce the issue from this code
"use client";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useState } from "react";
export default function EmailChange() {
const supabase = createClientComponentClient();
const [email, setEmail] = useState("");
const [otp, setOtp] = useState("");
const [otpSent, setOtpSent] = useState(false);
const handleSendOTP = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
const { data, error } = await supabase.auth.updateUser({ email });
if (error) {
console.log("Error from updateEmail ", JSON.stringify(error));
} else {
console.log("Email updation success ", JSON.stringify(data));
setOtpSent(true);
}
};
const handleVerifyOTP = async (e: React.MouseEvent<HTMLButtonElement>) => {
console.log("Call receives...");
e.preventDefault();
e.stopPropagation();
const { data, error } = await supabase.auth.verifyOtp({
token: otp,
type: "email_change",
email,
});
if (error) {
console.log(JSON.stringify(error));
}
if (data?.user) {
alert(`OTP ${otp} verified successfully!`);
}
};
return (
<div className="max-w-md mx-auto p-6 border rounded-lg shadow-lg mt-10 bg-gray-50">
<h2 className="text-2xl font-bold mb-4">OTP Verification</h2>
<div className="mb-4">
<label className="block text-gray-600">Email Address:</label>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded-md focus:outline-none focus:ring focus:border-blue-300"
/>
</div>
{!otpSent ? (
<button
onClick={handleSendOTP}
className="w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:border-blue-300"
>
Send OTP
</button>
) : (
<div>
<div className="mb-4">
<label className="block text-gray-600">Enter OTP:</label>
<input
type="text"
placeholder="Enter OTP"
value={otp}
onChange={(e) => setOtp(e.target.value)}
className="w-full p-2 border rounded-md focus:outline-none focus:ring focus:border-blue-300"
/>
</div>
<button
onClick={handleVerifyOTP}
className="w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:border-blue-300"
>
Verify OTP
</button>
</div>
)}
</div>
);
}
from looking at the docs, it does not exactly explain what it is used for, but by looking at your code, you might maybe refactor this snippet: const { data, error } = await supabase.auth.verifyOtp({ token: otp, type: "email_change", email, });
to this:
const { data, error } = await supabase.auth.verifyOtp({ token: otp, type: "email_change", email: email, });
because you do not use the properties in the order they are documented, I guess you have to specify the property names...
I have tried
const { data, error } = await supabase.auth.verifyOtp({
token: otp,
type: "email_change",
email: email,
});
and also tried this
const { data, error } = await supabase.auth.verifyOtp({
token: otp,
type: 'email',
email: email,
});
Still same error
Did you run the code with a User you know is definitely in the Users Table in Supabase? -> Does it still fails then?
Yes the user is in the supabase auth/user table. and it fails while verifyOtp
Are you using an older version of the local development environment? I was experiencing the same issue (local dev) and fixed it by upgrading to the latest CLI and local environment.
After looking through the GoTrue source I believe this was a bug in the verify
endpoint, where it was not calculating the token_hash
field from the token
and email
fields provided in the request (the token_hash
is an SHA-256 hash of the email+otp). It seems to have been corrected about 3-months ago.
GoTrue matches the provided (new) email
and token_hash
fields to the email_change
and email_change_token_new
columns of the auth.users
table, which get set after the initial updateUser
call. This is why it was failing to find the user when the token_hash
was missing.
I initially worked around the problem by manually calculating the token_hash
on the client side and providing it in the verifyOtp
call (token
also required). This worked, but was not ideal. Updating to the latest version of everything solved the issue, and it now works as it is presumably intended.
An incorrect OTP will generate a user not found response for the reasons mentioned.
In case you want to test providing the hash manually:
updated: algo and comments
// Will need a lib for SHA-224
const buf = await window.crypto.subtle.digest("SHA-224", newEmail + otp);
const tokenHash = Array.from(new Uint8Array(buf))
.map((item) => item.toString(16).padStart(2, "0"))
.join("");
const { data, error } = await supabase.auth.verifyOtp({
// Should be only [type, token_hash ] or [type, email, token] on latest version
type: "email_change",
email: newEmail,
token: otp,
token_hash: tokenHash
});
For reference I'm (now) using v2.38 of the JS client and v1.106.1 of the CLI.
@canardos , I am utilizing Spabase Cloud rather than a local CLI. Nevertheless, when attempting your solution, I encountered the following error message:
{"name":"AuthApiError","message":"Verify requires either a token or a token hash","status":400}
code:
const handleVerifyOTP = async (e: React.MouseEvent<HTMLButtonElement>) => {
console.log("Call receives...");
e.preventDefault();
e.stopPropagation();
const text = email + otp //'your data to hash';
const encoder = new TextEncoder();
const bufferText = encoder.encode(text);
const buf = await window.crypto.subtle.digest("SHA-256", bufferText);
const tokenHash = Array.from(new Uint8Array(buf))
.map((item) => item.toString(16).padStart(2, "0"))
.join("");
const { data, error } = await supabase.auth.verifyOtp({
token: otp,
type: "email",
email,
token_hash: tokenHash,
});
if (error) {
console.log(JSON.stringify(error));
}
if (data?.user) {
alert(`OTP ${otp} verified successfully!`);
}
};
It expects either a "token" or a "token_hash."
The issue arises when I attempt to change my email using the OTP option. When I use the link method, it functions as expected. I want to emphasize that I am certain I'm entering the correct OTP, as I've attempted it multiple times.
I am using latest version of the library
"dependencies": {
"@supabase/auth-helpers-nextjs": "latest",
"@supabase/supabase-js": "latest",
"autoprefixer": "10.4.15",
"next": "latest",
"postcss": "8.4.29",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.3.3",
"typescript": "5.1.3"
},
Verify requires either a token or a token hash
is the expected behavior when providing both the token
and token_hash
. I only had to provide both as I was using a version prior to this commit.
Given you're on the latest version, I suspect you have a different issue. You could try with the token_hash
only, or use it to manually compare again the hash in the auth.users
table, but I can't see it helping.
if I try with token_hash
only , i get this error
{"name":"AuthApiError","message":"Database error finding user from email link","status":500}
:(
Apologies, but my earlier code had the wrong algo (I'm using node (crypto.createHash("sha224").update(newEmail + otp).digest("hex").toLowerCase()
) and copied/adjusted for the browser). It should be SHA-224. I don't think SHA-224 is supported in the browser, but you can easily just calculate it manually here and see if it matches the hash in the auth.users
table. Or you could use a lib.
Also, you have type: "email"
, but is should be type: "email_change"
Finally, if you're providing the token_hash
, then on the latest version I think you should only provide type
and token_hash
.
That said, it should be working with just type
, email
, and token
, so clearly something else is wrong.
@abiddraws can you please send us a support ticket at https://supabase.com/dashboard/support/new ? it will be much easier for us to figure out what went wrong here with your project details!
I believe the issue is with the type "email_change". According to Supabase docs it states that the type is "email_change" but instead it should be "emailChange".
@scottwyn13 it should be email_change
as defined in the types here - https://github.com/supabase/auth-js/blob/64a926c2415f2bb6d9da52871c5bfe7bfd7c0eac/src/lib/types.ts#L650
closing since i'm unable to reproduce this error
Bug report
When a signed user attempts to change their email address. OTP receives to the new email. however when we try to verifyOtp I get an error saying "User not found". This is happening only when we try to change email using type: email_change. other types like signup are working fine.
To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:
const { data, error } = await supabase.auth.updateUser({ email });
const { data, error } = await supabase.auth.verifyOtp({ token: otp, type: "email_change", email, /* new email */ });
{"name":"AuthApiError","message":"User not found","status":404}
Expected behavior
Signed user should be able to change their email with OTP verification
System information
-Browser chrome