iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Software Development

我獨自開發 - 用 Supabase 打造全端應用系列 第 28

第二十八關 - 來企排隊: Supabase 快速建立手機號碼登入

  • 分享至 

  • xImage
  •  

封面

除了使用信箱驗證註冊以外,今天要介紹透過 Supabase Auth 與 Twilio SMS 服務,建立手機號碼登入系統,讓使用者無需記住密碼,只要輸入手機號碼接收驗證碼即可快速登入。

主要功能

  • 支援國際手機號碼格式
  • 自動發送 SMS 驗證碼
  • 驗證碼有效期限控制
  • 一次性驗證碼(OTP)

第一步:申請 Twilio SMS 服務

1.1 註冊 Twilio 帳號

Twilio 是通訊 API 服務商,提供穩定的 SMS 發送服務,而且有免費額度可做開發測試,也可以使用預設 OTP 來測試,無需實際發送 SMS。

  1. 前往 Twilio 官網:訪問 https://www.twilio.com
  2. 點擊 "Start building for free" 開始註冊

1.2 設定 Twilio 專案

註冊完成後,需要進行基本設定:

  1. 選擇使用目的:選擇 "SMS" 作為主要用途
  2. 選擇程式語言:選擇 "JavaScript/Node.js"

1.3 購買電話號碼

Twilio 需要一個專用號碼來發送 SMS:

  1. 進入 Phone Numbers 頁面
  2. 點擊 "Buy a number"
  3. 選擇國家和號碼類型
    • 選擇您的服務地區
    • 確保支援 SMS 功能
  4. 完成購買:免費試用帳號通常有 $15 美金額度
  5. 取得 Account SID 和 Auth Token
    • 在 Twilio Console 首頁可以看到
    • Account SID:類似 ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    • Auth Token:點擊眼睛圖示顯示完整 Token
    • Sender Phone Number:購買的 Twilio 號碼,例如 +1234567890

第二步:Supabase 專案設定

2.1 啟用手機號碼登入

在 Supabase Dashboard 中設定手機登入:

  1. 進入 Authentication 設定

    • 登入 Supabase Dashboard
    • 選擇您的專案
    • 點擊左側選單的 "Authentication"
  2. 啟用 Phone 登入方式

    • 點擊 "Sign In / Providers" 標籤
    • 在 "Auth Providers" 區域找到 "Phone"
    • 將 "Enable phone provider" 開關打開

2.2 設定 Twilio 整合

在 Supabase 中設定 Twilio 憑證:

  1. 填入 Twilio 憑證

    Twilio Account SID: ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    Twilio Auth Token: your_auth_token_here
    Twilio Message Service SID: your_sender_phone_number_here
    
  2. 設定 SMS 模板(可選):

    Your verification code is: {{ .Code }}
    

2.3 環境變數設定

在您的專案中設定環境變數:

# .env.local
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key

第三步:前端實作

建立手機登入表單元件:

// components/phone-login-form.tsx

export default function PhoneLoginForm() {
  // 發送驗證碼
  const sendOTP = async (e: React.FormEvent) => {
    try {
      const { error } = await supabase.auth.signInWithOtp({
        phone: phone,
      });

      if (error) {
        setMessage(`錯誤:${error.message}`);
      } else {
        setMessage("驗證碼已發送到您的手機");
        // 切換到 OTP 驗證步驟
        setStep("otp");
      }
    } catch (error) {
      setMessage("發送驗證碼時發生錯誤");
    } finally {
      setLoading(false);
    }
  };

  // 驗證 OTP 並登入
  const verifyOTP = async (e: React.FormEvent) => {
    try {
      const { data, error } = await supabase.auth.verifyOtp({
        phone: phone,
        token: otp,
        type: "sms",
      });

      if (error) {
        setMessage(`驗證失敗:${error.message}`);
      } else {
        setMessage("登入成功!");
        // 登入成功後的處理
        window.location.href = "/home";
      }
    } catch (error) {
      setMessage("驗證時發生錯誤");
    } finally {
      setLoading(false);
    }
  };

  // 重新發送驗證碼
  const resendOTP = async () => {
    setLoading(true);
    try {
      const { error } = await supabase.auth.signInWithOtp({
        phone: phone,
      });

      if (error) {
        setMessage(`重發失敗:${error.message}`);
      } else {
        setMessage("驗證碼已重新發送");
      }
    } catch (error) {
      setMessage("重發驗證碼時發生錯誤");
    } finally {
      setLoading(false);
    }
  };

  return <form>...</form>;
}

第四步:進階功能

4.1 手機號碼格式驗證

加入手機號碼格式驗證:

// utils/phone-validation.ts

export function validatePhoneNumber(phone: string): boolean {
  // 基本的國際手機號碼格式檢查
  const phoneRegex = /^\+[1-9]\d{1,14}$/;
  return phoneRegex.test(phone);
}

export function formatPhoneNumber(phone: string): string {
  // 移除所有非數字字符,保留 + 號
  return phone.replace(/[^\d+]/g, "");
}

// 常見國家的手機號碼格式
export const phoneFormats = {
  TW: { code: "+886", example: "+886912345678", length: 13 },
  US: { code: "+1", example: "+1234567890", length: 12 },
  CN: { code: "+86", example: "+8613812345678", length: 14 },
  JP: { code: "+81", example: "+819012345678", length: 13 },
};

4.2 錯誤處理

改善錯誤處理和使用者回饋:

// 在 PhoneLoginForm 中加入錯誤處理
const handleError = (error: any) => {
  console.error("Phone login error:", error);

  // 根據不同錯誤類型提供錯誤訊息
  if (error.message?.includes("Invalid phone number")) {
    setMessage("手機號碼格式不正確,請確認包含國碼");
  } else if (error.message?.includes("SMS not sent")) {
    setMessage("簡訊發送失敗,請稍後再試");
  } else if (error.message?.includes("Invalid token")) {
    setMessage("驗證碼不正確,請重新輸入");
  } else if (error.message?.includes("Token expired")) {
    setMessage("驗證碼已過期,請重新發送");
  } else {
    setMessage("發生未知錯誤,請稍後再試");
  }
};

4.3 安全性考量

實作安全性:

// 限制重發驗證碼的頻率
const [canResend, setCanResend] = useState(true);
const [countdown, setCountdown] = useState(0);

const resendOTP = async () => {
  if (!canResend) return;

  setLoading(true);
  setCanResend(false);
  setCountdown(60); // 60 秒倒數

  // 倒數計時器
  const timer = setInterval(() => {
    setCountdown((prev) => {
      if (prev <= 1) {
        clearInterval(timer);
        setCanResend(true);
        return 0;
      }
      return prev - 1;
    });
  }, 1000);

  try {
    const { error } = await supabase.auth.signInWithOtp({
      phone: phone,
    });

    if (error) {
      setMessage(`重發失敗:${error.message}`);
    } else {
      setMessage("驗證碼已重新發送");
    }
  } catch (error) {
    setMessage("重發驗證碼時發生錯誤");
  } finally {
    setLoading(false);
  }
};

第五步:測試與部署

測試手機登入功能:

# 啟動開發伺服器
pnpm run dev

# 訪問手機登入頁面
# http://localhost:3000/auth/phone-login

測試

  • 驗證碼發送功能
  • 錯誤輸入處理
  • 驗證碼驗證功能
  • 重發驗證碼功能
  • 登入成功後的導向

小結

手機號碼登入為用程式提供了更便利的使用者體驗,同時保持了高度的安全性。

... to be continued

有任何想討論歡迎留言,或需要指正的地方請鞭大力一點,歡迎訂閱、按讚加分享,分享給想要提升開發效率的朋友


上一篇
第二十七關 - 來企排隊: Supabase 快速整合 Resend 訂單通知信
系列文
我獨自開發 - 用 Supabase 打造全端應用28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言