手机号 + 一次性验证码(OTP)是移动端最友好的登录方式。本文示范如何仅用 Expo + Better Auth + Azure Communication Services (ACS) 在 30 分钟内完成:
- 购买并绑定支持 SMS 的 Azure 电话号码
- 在 Clinic Dashboard 暴露一个
/api/sms端点,负责发送 OTP - 在 Expo 客户端通过 Better Auth 的 Phone Number 插件完成登录
- 登录成功后把患者信息持久化到 PostgreSQL
整体架构
markdown
┌────────────┐ HTTPS ┌─────────────────┐
│ Expo App │◄──────────────►│ Clinic Dashboard│
│ │ │ Node/Next.js │
└────▲───────┘ └──────┬──────────┘
│ SMS OTP │
│ │ ACS SDK
│ ▼
│ ┌──────────────────────┐
│ │ Azure Comm. Services │
│ │ SMS Gateway │
└────────────────────┴──────────────────────┘
步骤 1:购买 Azure SMS 电话号码
- 登录 Azure Portal
- Communication Services → 电话号码 → 获取
- 选择:
• 国家/地区(与账单地址匹配)
• 类型:Local 或 Toll-Free
• 功能:✅ 发送短信 - 搜索 → 添加到购物车 → 立即购买
- 几分钟后状态为 已预配 ,记下号码
+1xxxxxxxxxx
步骤 2:Clinic Dashboard 暴露 /api/sms
安装依赖
bash
npm i @azure/communication-sms dotenv
.env
ini
AZURE_CONNECTION_STRING=endpoint=https://xxx.communication.azure.com/;accesskey=xxx
AZURE_SMS_FROM=+15551234567 # 刚买的号码
/pages/api/sms.ts(Next.js 13 App Router 同理)
ts
import { SmsClient } from "@azure/communication-sms";
import "dotenv/config";
const sms = new SmsClient(process.env.AZURE_CONNECTION_STRING);
export default async function handler(req, res) {
if (req.method !== "POST") return res.status(405).end();
const { phone, code } = req.body;
if (!phone || !code) return res.status(400).json({ error: "missing fields" });
await sms.send({
from: process.env.AZURE_SMS_FROM,
to: [`+${phone}`],
message: `Your verification code is ${code}`,
});
res.json({ success: true });
}
部署后得到 HTTPS 端点 https://api.clinic.com/api/sms
步骤 3:Expo 端集成 Better Auth
安装依赖
bash
npm i better-auth @better-auth/expo
npx expo install expo-secure-store expo-web-browser
auth.ts
ts
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo";
export const authClient = createAuthClient({
baseURL: "https://api.clinic.com",
plugins: [expoClient()],
});
步骤 4:Better Auth 服务端配置
安装
bash
npm i better-auth drizzle-orm pg
better-auth.ts
ts
import { betterAuth } from "better-auth";
import { phoneNumber } from "better-auth/plugins";
export const auth = betterAuth({
database: { provider: "pg", url: process.env.DATABASE_URL! },
plugins: [
phoneNumber({
sendOTP: async ({ phone, code }) => {
await fetch("https://api.clinic.com/api/sms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone, code }),
});
},
}),
],
});
步骤 5:Expo 登录界面示例
ts
import { useState } from "react";
import { View, TextInput, Button, Alert } from "react-native";
import { authClient } from "./auth";
export default function LoginScreen() {
const [phone, setPhone] = useState("");
const [otp, setOtp] = useState("");
const send = async () => {
const res = await authClient.phoneNumber.sendOtp({ phone });
res.error && Alert.alert("Error", res.error.message);
};
const verify = async () => {
const res = await authClient.phoneNumber.verifyOtp({ phone, code: otp });
if (!res.error) {
// 成功:跳转到主界面
} else {
Alert.alert("Error", res.error.message);
}
};
return (
<View>
<TextInput
placeholder="+1234567890"
value={phone}
onChangeText={setPhone}
/>
<Button title="Send OTP" onPress={send} />
<TextInput
placeholder="6-digit code"
value={otp}
onChangeText={setOtp}
keyboardType="numeric"
/>
<Button title="Verify" onPress={verify} />
</View>
);
}
八、持久化患者信息
verifyOtp 时可携带附加字段:
ts
await authClient.phoneNumber.verifyOtp({
phone,
code: otp,
additionalFields: {
name: "Alice Smith",
dob: "1990-01-01",
role: "patient",
},
});
Better Auth 会自动写入 users.meta 或自定义 patients 表。
九、本地开发小技巧
• 用 npx ngrok http 3000 暴露后端,替换 baseURL
• 开发构建:
npx expo run:ios 或 npx expo run:android(Expo Go 不支持自定义 scheme)
十、安全与生产建议
- OTP 存入 Redis,TTL 5 分钟,验证后删除
/api/sms做速率限制:同一手机号 60 秒 1 次- 切勿把 Azure Connection String 暴露到客户端
- 可选:启用 ACS Number Lookup API 预先验证号码是否可达
十一、一分钟速查清单
| 任务 | 状态 |
|---|---|
| Azure 订阅已付费 | ✅ |
| 已购买支持 SMS 的号码 | ✅ |
/api/sms 部署并 HTTPS |
✅ |
| Better Auth server 已配置 | ✅ |
| Expo 客户端集成完成 | ✅ |
十二、参考链接
• Better Auth Phone Number Docs
• Azure Communication Services SMS Quickstart
至此,一个可上线、可扩展的 Azure SMS-OTP 登录体系便搭建完毕。祝开发顺利!# 在 Expo 中实现 Azure SMS-OTP 登录