使用Twilio Verify和React Native增加流畅、安全和无声的设备授权

398 阅读10分钟

如果你能为你的用户提供持续的认证,而不需要密码或向他们发送一次性的密码,那会怎么样?有了Twilio的Verify Push API,你就可以做到了。

Verify Push API的强大之处不仅仅在于它的推送通知部分。该API使用公钥加密技术,将任何设备变成一个安全的密钥。这允许你的应用程序注册受信任的设备,并使用它们作为强大的验证器。当认证在注册的设备上完成时,一切都可以在后台默默地发生,没有任何用户参与。这降低了摩擦,增加了可用性,并仍然提供强大的安全性。

以下是你在本教程中要建立的流程:

  1. 用户被注册/认证(在我们的例子中使用短信验证,但也可以是用户名/密码)
  2. 设备被注册为安全密钥("因子")。
  3. 用户尝试登录
  4. 应用程序默默地对用户进行认证("挑战")。

本教程不包括 "推送通知 "组件,但如果你选择这样做,你完全可以启用它

如果你想跳过前面的内容,可以在我的GitHub上找到完整的项目。

设置你的开发环境

设置React Native CLI

对于本教程,你只需要设置一个目标操作系统(Android或iOS)。按照React Native CLI快速入门标签下的说明,选择您的开发操作系统和目标操作系统。 "创建一个新的应用程序 "之前停止。

你将用于无声审批的React Native验证推送SDK与Expo不兼容,所以你必须使用React Native CLI设置。

从Code Exchange部署两个应用程序

在继续之前,你需要一个Verify Push的样本后台,你可以从我们的代码交换中心点击几下就可以部署。使用默认的 "哈希 "值进行身份处理,这将混淆任何PII。要完成这一步,你需要你的验证服务的服务SID

你还需要部署这个来自Code Exchange的一次性密码验证应用程序,以便使电话验证部分发挥作用。这个应用程序将使用相同的验证服务SID。部署后,点击进入实时应用程序,这将在你的浏览器中打开一个新标签。暂时保持打开状态。

克隆启动程序

你需要一个应用程序来构建无声审批,你可以使用:

  1. 一个现有的应用程序
  2. 基于本博文中建立的电话验证的启动程序

如果你已经有一个可以添加无声授权的应用,你可以跳到下一步。如果你没有,请继续阅读以获得预建的启动程序。

在你的终端,导航到一个合适的工作目录,并运行以下命令:

git clone -b starter https://github.com/robinske/verify-push-silent-auth-react-native.git && cd verify-push-silent-auth-react-native

接下来,用(npx pod-install仅在iOS目标中需要)安装依赖项:

yarn install
npx pod-install

打开你用上述命令创建的文件夹,验证-push-silent-auth-react-native。在这个文件夹中,你会看到一个名为*.env.example的文件。将该文件的名称改为.env*(去掉*.example*扩展名)。

在你喜欢的文本编辑器中打开你的*.env*文件,并寻找以下突出显示的一行:

# Follow instructions to deploy this project
# https://www.twilio.com/code-exchange/one-time-passcode-verification-otp
# will look like https://verify-xxxx-xxxxxx.twil.io
BASE_URL=

从你从Code Exchange部署的一次性密码应用中找到你的基本URL。它将是实时应用程序的URL的第一部分。它应该看起来像verify-XXXX-bohzzb.twil.io。

把这个BASE_URL 添加到你的.env文件中(这里有详细描述)。

测试你的应用程序

通过运行yarn iosyarn android 来测试你的应用程序,这取决于你喜欢的目标操作系统。如果你遇到任何问题,在这篇博文中有更多关于构建和运行应用程序的细节。

如果你在任何时候被卡住了,这里有启动程序和本教程所涉及的全部代码差异。

如何将一个设备注册为安全钥匙:验证因素

你需要做的第一件事是注册设备。你可以用Verify Push SDK来做这件事,它将在设备上生成一个密钥对,并将公钥发送给Twilio,这样你就可以直接使用API而不是自己做密钥管理。

你可以通过注册一个Factor来做到这一点。

在你克隆的启动程序中,打开src/screens/RegisterPush.tsx。这是一旦有人注册或认证,并且你足够信任他们,让他们把设备注册为安全密钥时,你将显示的屏幕。做这个的好时机包括在注册时或登录后。

用以下代码替换现有的RegisterPush 组件的内容:

const [spinner, setSpinner] = useState(false);
 const { phoneNumber } = route.params;
 return (
   <SafeAreaView style={commonStyles.wrapper}>
     <Spinner
       visible={spinner}
       textContent={"One moment..."}
       textStyle={commonStyles.spinnerTextStyle}
     />
     <Text style={commonStyles.prompt}>
       Secure your account with this device?
     </Text>
     <Text style={commonStyles.message}>
       Whenever there's a new login, we'll send a notification to this phone. It's safer than a text message and you can instantly approve or deny access.
     </Text>
     <TouchableOpacity
       style={{ backgroundColor: "#36D576", ...styles.button }}
       onPress={() => {
         setSpinner(true);
       }}
     >
       <Text style={commonStyles.buttonText}>Yes, use this device</Text>
     </TouchableOpacity>
     <TouchableOpacity
       style={{ backgroundColor: "#AEB2C1", ...styles.button }}
       onPress={() => console.log("skipping push registration")}
     >
       <Text style={commonStyles.buttonText}>Not now</Text>
     </TouchableOpacity>
   </SafeAreaView>
 );

这将呈现以下屏幕:

phone screen asking "secure your account with this device" with yes and no buttons

加密身份以混淆PII

接下来,你需要实际处理当用户点击是,使用这个设备。打开src/api/verify.js。你要用下面的步骤来填写createFactor() 函数。

这段代码使用用户的电话号码作为身份识别的基础,所以你要把它模糊化。在文件的顶部,就在存在import 行的下面,导入sha256 方法:

import { sha256 } from "react-native-sha256";

然后在createFactor() 函数中添加以下代码:

const identity = await sha256(phoneNumber);

创建因子

SDK文档告诉你如何创建一个因子,并解释说你需要以下参数:

  • factorName
  • verifyServiceSid
  • identity
  • pushToken
  • accessToken

你已经有了#2和#3。

因素名称可以是任何字符串,但你也可以使用react-native-device-info 库来抓取设备名称(如 "Kelley的iPhone 12")。导入该库(如果你使用的是启动项目,它应该已经安装好了,否则请按照安装说明进行操作:

import { getDeviceName, getDeviceToken } from "react-native-device-info";

createFactor() 函数中添加以下内容:

const deviceName = await getDeviceName().catch(
 () => `${phoneNumber}'s Device'`
);

const deviceToken = await getDeviceToken().catch(
 () => "000000000000000000000000000000000000"
);

这将照顾到factorNamepushToken 。你需要的最后一件事是一个访问令牌,以便从SDK中与Twilio的API通信。

如果你还没有,请使用你的Verify Service SID部署这个Verify Push Backend。打开你的.env 文件并添加PUSH_BACKEND_URL

# will look like https://verify-push-backend-1234-abcdef.twil.io
PUSH_BACKEND_URL=<your verify push backend url here>

在对身份进行哈希运算后,在createFactor() 函数中添加以下内容:

const response = await fetch(`${PUSH_BACKEND_URL}/access-token`, {
 method: "POST",
 headers: {
   Accept: "application/json",
   "Content-Type": "application/json",
 },
 body: JSON.stringify({
   identity,
 }),
});

const json = await response.json();

在这一点上,你有我们所需要的一切来创建因子。

添加Twilio Verify Push React Native SDK

在终端上,安装Verify Push SDK

yarn add https://github.com/twilio/twilio-verify-for-react-native.git

如果你的目标是iOS,还要安装Pods:

npx pod-install

verify.js中,导入你需要的SDK组件以创建一个因子:

import TwilioVerify, {
 PushFactorPayload,
 VerifyPushFactorPayload,
} from "@twilio/twilio-verify-for-react-native";

createFactor() 函数中添加以下代码:

const payload = new PushFactorPayload(
  deviceName,
  json.serviceSid,
  json.identity,
  deviceToken,
  json.token
);

let factor = await TwilioVerify.createFactor(payload);

最后,立即验证Factor并存储Factor SID供将来参考:

factor = await TwilioVerify.verifyFactor(
  new VerifyPushFactorPayload(factor.sid)
);

AsyncStorage.setItem("@factor_sid", factor.sid);
AsyncStorage.setItem("@identity", identity);

return factor.sid;

下面是最后的createFactor() 函数的样子:

export const createFactor = async (phoneNumber) => {
 // identity should not contain PII
 const identity = await sha256(phoneNumber);

 const response = await fetch(`${PUSH_BACKEND_URL}/access-token`, {
   method: "POST",
   headers: {
     Accept: "application/json",
     "Content-Type": "application/json",
   },
   body: JSON.stringify({
     identity,
   }),
 });

 const json = await response.json();

 const deviceName = await getDeviceName().catch(
   () => `${phoneNumber}'s Device'`
 );

 const deviceToken = await getDeviceToken().catch(
   () => "000000000000000000000000000000000000"
 );

 const payload = new PushFactorPayload(
   deviceName,
   json.serviceSid,
   json.identity,
   deviceToken,
   json.token
 );

 let factor = await TwilioVerify.createFactor(payload);
 factor = await TwilioVerify.verifyFactor(
   new VerifyPushFactorPayload(factor.sid)
 );

 AsyncStorage.setItem("@factor_sid", factor.sid);
 AsyncStorage.setItem("@identity", identity);

 return factor.sid;
};

触发设备注册

回到RegisterPush.tsx文件中,更新Yes, use this device按钮来调用你新的createFactor() 函数。

导入该函数:

import { createFactor } from "../api/verify";

用下面的代码替换onPress() 方法(在TouchableOpacity 组件里面,就在 "每当有新的登录 "消息下面):

onPress={() => {
 setSpinner(true);
 createFactor(phoneNumber)
   .then(() => {
     setSpinner(false);
     navigation.replace("Gated");
   })
   .catch((e) => {
     console.error(e);
   });
}}

最后,在src/screens/Otp.tsx中更新导航,以提示设备注册,其中有如下说明 *// TODO - add a step to allow user to register device as a secure key*:

- success && navigation.replace("Gated");
+ success && navigation.replace("RegisterPush", { phoneNumber });

在这一点上,你可以测试一下因子的创建。确保你的*.env*文件的来源,以获取新的变量,并用yarn iosyarn android 重新启动应用程序。 完成电话验证,你应该能够点击是,使用这个设备,并被重新引导到银行图像。潜在的错误应该被记录在Metro窗口中,以帮助你调试任何问题。

默默地授权未来的登录

现在设备已经注册,你可以像钥匙一样使用设备,而不是发送短信OTP或要求密码。

本节将构建欢迎屏幕上的登录按钮背后的功能。

src/screens/Welcome.tsx中,在Welcome() 组件函数中寻找写着// TODO add silent authorization 的那一行。它应该在第21行的某个地方。用下面的导航来替换这个注释:

navigation.replace("Verifying")

然后到*/src/api/verify.js*去写你的函数来发出挑战。挑战书是Verify API跟踪验证尝试的方式。SDK将用你注册的因子与Verify API对话,以验证该设备是否有效。由于你是在设备上完成的,所以它可以在后台无缝地进行,因此沉默授权中的 "沉默 "是指。

有4个步骤来完成无声授权:

  1. 从存储器中获取身份
  2. 创建新的挑战
  3. 立即验证挑战
  4. 检查挑战状态以进行验证

这些步骤中的每一步都将在下面的章节中描述。

从存储器中获取身份

silentAuthorization() 函数希望传入factorSid ,所以你只需要从存储空间获取身份,以便创建挑战。

silentAuthorization() 函数中的try 块内,在TODO 的注释中加入以下代码:

const identity = await AsyncStorage.getItem("@identity");

const data = JSON.stringify({
 identity,
 factor: factorSid,
 message: "Login request",
});

创建新的挑战

你还需要用于创建挑战的端点。幸运的是,你用于访问令牌端点的后端包括一个。在你定义了数据之后,通过调用端点来创建一个挑战。在你上一步添加的代码之后,立即添加以下内容:

const response = await fetch(`${PUSH_BACKEND_URL}/create-challenge`, {
 method: "POST",
 headers: {
   "Content-Type": "application/json",
 },
 body: data,
});

const json = await response.json();
const challengeSid = json.sid;

立即验证挑战

首先,从Verify Push SDK中导入必要的组件。在同一个verify.js文件的顶部,编辑你的导入,在VerifyPushFactorPayload

UpdatePushChallengePayload,
ChallengeStatus,

silentAuthorization() 函数中的try 块的末尾添加以下代码,在您在前面步骤中添加的代码下面,以 "更新 "挑战 - 这就是进行验证的原因:

const payload = new UpdatePushChallengePayload(
 factorSid,
 challengeSid,
 ChallengeStatus.Approved
);
let updated = await TwilioVerify.updateChallenge(payload);
updated = await TwilioVerify.getChallenge(challengeSid, factorSid);

检查挑战状态以进行验证

最后,检查状态以确保它被批准并返回结果:

return updated.status === ChallengeStatus.Approved;

登录时触发无声授权

到*/src/screens/Verifying.tsx*去建立授权检查。该组件有一些模板,让用户了解发生了什么。

验证推送挑战是非常安全和非常快速的。我们建议向用户展示有关该过程的信息,以便他们知道他们的账户受到保护。

你将使用React Hook在页面加载时触发验证,因为你不要求用户再点击任何按钮。

用以下代码替换// TODO

useEffect(() => {
 AsyncStorage.getItem("@factor_sid")
   .then((factorSid) => {
     silentAuthorization(factorSid).then((approved) => {
       if (approved) {
         navigation.replace("Gated");
       }
     });
   })
   .catch((e) => {
     console.error(e);
     navigation.replace("PhoneNumber");
   });
}, [isFocused]);

来自导航库的isFocused 帮助器是必要的,它可以告诉组件在页面加载时有一个状态变化并触发useEffect 钩子。

重新加载应用程序并尝试点击登录。你应该能短暂地看到验证屏幕。因为它发生得太快了,你可以考虑建立一个超时器,向用户显示至少1或2秒的信息。

这是完整的代码,你应该在本教程结束时拥有

推送通知呢?

你使用验证推送API来构建这个演示,但实际上并没有对推送通知做任何事情。你绝对可以添加这个功能,并将移动设备作为桌面登录、呼叫中心认证等的关键。在SDK文档中了解更多关于如何启用推送通知的信息。

像这样的设备授权的挑战之一是如何管理账户恢复:当终端用户失去他们的设备时,会发生什么?更多的开发者看到了推送作为第一层无摩擦防御的价值,然后会在设备不可用的情况下回退到其他渠道,如一次性密码。Verify API提供了许多后退渠道。