Hi, I am able to log in and sign up users with email and password, but I also want to include phone verification, which I haven’t been able to implement successfully. I’ve tried two approaches, but both failed.
Approach 1:
Here I use superbase Auth update approach which is failed to fetch user
Code component otp verifications:
import { useState } from 'react';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
export function OtpVerificationComponent({ userEmail, userId }) {
const [phoneNumber, setPhoneNumber] = useState('');
const [otpSent, setOtpSent] = useState(false);
const [otp, setOtp] = useState('');
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const sendOtp = async () => {
if (!userEmail || !userId) {
setError('No logged-in user. Please log in first.');
return;
}
try {
const { error } = await supabase.auth.updateUser({
phone: phoneNumber,
});
if (error) {
setError(`Failed to send OTP: ${error.message}`);
} else {
setOtpSent(true);
setError('');
}
} catch (error) {
setError(`An error occurred while sending OTP: ${error.message}`);
}
};
const verifyOtp = async () => {
if (!userEmail || !userId) {
setError('No logged-in user. Please log in first.');
return;
}
try {
const { data, error } = await supabase.auth.verifyOtp({
phone: phoneNumber,
token: otp,
type: 'phone_change',
});
if (error) {
setError(`Invalid OTP: ${error.message}`);
} else {
setSuccess('Phone number verified and updated successfully.');
setError('');
}
} catch (error) {
setError('An error occurred while verifying OTP: ' + error.message);
}
};
return (
<div
style={{
width: '100%',
height: '100%',
position: 'fixed',
top: 0,
left: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 50,
}}
>
<div
style={{
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
padding: '24px',
width: '90%',
maxWidth: '400px',
}}
>
<div
style={{
fontSize: '24px',
fontWeight: 'bold',
marginBottom: '16px',
}}
>
Verify Mobile No
</div>
{!otpSent ? (
<div style={{ marginBottom: '16px', width: '100%' }}>
<label
style={{
display: 'block',
marginBottom: '8px',
fontSize: '14px',
fontWeight: '500',
}}
>
Enter your phone number with country code:
</label>
<input
type="text"
placeholder="+91xxxxxxxxxx"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
style={{
width: '94%',
padding: '8px 12px',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '16px',
}}
/>
<div
style={{
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: '16px',
}}
>
<button
onClick={sendOtp}
style={{
width: '100px',
backgroundColor: 'black',
color: 'white',
padding: '10px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
opacity: 1,
}}
>
Send OTP
</button>
</div>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
) : (
<div style={{ marginBottom: '16px' }}>
<label
style={{
display: 'block',
marginBottom: '8px',
fontSize: '14px',
fontWeight: '500',
}}
>
Enter the OTP sent to {phoneNumber}:
</label>
<input
type="text"
placeholder="Enter OTP"
value={otp}
onChange={(e) => setOtp(e.target.value)}
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '16px',
}}
/>
<button
onClick={verifyOtp}
style={{
width: '100px',
backgroundColor: 'black',
color: 'white',
padding: '10px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
opacity: 1,
}}
>
Verify OTP
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
)}
{success && <p style={{ color: 'green' }}>{success}</p>}
</div>
</div>
);
}
Approach 2:
where i use signwith otp and able to send otp and verify the phone no but not able to update user.
to update user i have build a superbase edge function but not able to update the user
code component otpVerfication:
import { useState } from 'react';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
export function OtpVerificationComponent({ userEmail, userId }) {
const [phoneNumber, setPhoneNumber] = useState('');
const [otpSent, setOtpSent] = useState(false);
const [otp, setOtp] = useState('');
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
console.log(`usermail ${userEmail} userIdc ${userId}`)
const sendOtp = async () => {
if (!userEmail || !userId) {
setError('No logged-in user. Please log in first.');
return;
}
try {
const { error } = await supabase.auth.signInWithOtp({
phone: phoneNumber,
});
if (error) {
setError(`Failed to send OTP: ${error.message}`);
} else {
setOtpSent(true);
setError('');
}
} catch (error) {
setError(`An error occurred while sending OTP: ${error.message}`);
}
};
const verifyOtp = async () => {
if (!userEmail || !userId) {
setError('No logged-in user. Please log in first.');
return;
}
try {
const { data, error } = await supabase.auth.verifyOtp({
phone: phoneNumber,
token: otp,
type: 'sms',
});
if (error) {
setError(`Invalid OTP: ${error.message}`);
} else {
setSuccess('Phone number verified successfully');
setError('');
try {
const response = await fetch(
'https://ldgbeyuusmyliblkcnyh.supabase.co/functions/v1/updatePhone',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_UPDATE_PHONE_AUTH_TOKEN}`
},
body: JSON.stringify({ userId, phone: phoneNumber }),
}
);
const result = await response.json();
if (response.ok) {
setSuccess('Phone number updated successfully.');
} else {
setError(`Failed to update phone number: ${result.error}`);
}
} catch (updateError) {
setError('An error occurred while updating the phone number.');
console.log(updateError);
}
}
} catch (error) {
setError('An error occurred while verifying OTP: ' + error.message);
}
};
return (
<div style={{
width: '100%',
height: '100%',
position: 'fixed',
top: 0,
left: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 50
}}>
<div style={{
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
padding: '24px',
width: '90%',
maxWidth: '400px'
}}>
<div style={{ fontSize: '24px', fontWeight: 'bold', marginBottom: '16px' }}>Verify Mobile No</div>
{!otpSent ? (
<div style={{ marginBottom: '16px', width:'100%' }}>
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500' }}>
Enter your phone number with country code:
</label>
<input
type="text"
placeholder="+91xxxxxxxxxx"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
style={{
width: '94%',
padding: '8px 12px',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '16px'
}}
/>
<div style={{ flexDirection: "row", justifyContent: "flex-end", marginTop: '16px' }}>
<button onClick={sendOtp} style={{
width: '100px',
backgroundColor: 'black',
color: 'white',
padding: '10px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
opacity: 1
}}>Send OTP</button>
</div>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
) : (
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500' }}>
Enter the OTP sent to {phoneNumber}:
</label>
<input
type="text"
placeholder="Enter OTP"
value={otp}
onChange={(e) => setOtp(e.target.value)}
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '16px'
}}
/>
<button onClick={verifyOtp} style={{
width: '100px',
backgroundColor: 'black',
color: 'white',
padding: '10px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
opacity: 1
}}>Verify OTP</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
)}
{success && <p style={{ color: 'green' }}>{success}</p>}
</div>
</div>
);
}
edge function:
import { createClient } from "jsr:@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
console.log(`Function "updatePhoneNumber" up and running!`);
Deno.serve(async (req) => {
// Handle CORS preflight requests
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
const { userId, phone } = await req.json();
if (!userId || !phone) {
throw new Error("userId and phone are required");
}
const supabaseUrl = Deno.env.get("SUPABASE_URL");
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
const supabase = createClient(supabaseUrl, supabaseServiceKey);
// Format phone number
const formattedPhone = phone.toString().startsWith('+') ? phone.toString() : `+${phone}`;
// Update phone in user metadata - this is the most reliable approach
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
user_metadata: {
phone: formattedPhone,
phone_verified: true
}
});
if (error) {
throw error;
}
// Also store in a profiles table if you have one
try {
await supabase
.from('profiles') // Change 'profiles' to your actual table name if different
.upsert(
{
id: userId,
phone: formattedPhone,
updated_at: new Date().toISOString()
},
{ onConflict: 'id' }
);
} catch (profileError) {
// Continue even if profile update fails
console.error("Profile update error:", profileError);
}
return new Response(JSON.stringify({
success: true,
message: "Phone number updated successfully",
userId: userId,
phone: formattedPhone
}), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
status: 200,
});
} catch (error) {
console.error("Error:", error);
return new Response(JSON.stringify({
success: false,
error: error.message || "Unknown error"
}), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
status: 400,
});
}
});
references:
please help me to update the user phone no with otp verification