# 接入 MFA

## 前期准备

1. [注册一个 Authing 账号](https://authing.cn/login)。
2. [创建一个用户池](/authing/quickstart/basic.md#yong-hu-chi)。
3. [创建一个 OIDC 应用](/authing/authentication/oidc/create-oidc.md)。
4. 建议安装 http-server 用于快速启动 http 服务器来查看 html 文件。`npm install -g http-server`

## 所需技能

1. 基础 HTML 知识
2. 中级 Javascript 知识

## 新建一个空白 html 文件用于编写 Authing 程序

本教程只是为了演示，因此我们没选择高级框架，这可以让我们专注于 MFA 逻辑本身。

```markup
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Authing MFA Example</title>
  </head>
  <body></body>
</html>
```

## 添加基本控件

为我们的 html 添加四个输入框和三个按钮。用于输入用户名、密码、验证码、MFA 口令等信息。我们使用 jquery 的插件来生成 MFA 令牌的二维码。

```markup
<div class="row">
  <label>用户名：<input id="username"/></label>
  <label>密码：<input id="password"/></label>
  <label>验证码：<input id="verify-code"/><img id="verify-img" style="vertical-align: bottom;"/></label>
</div>
<div id="mfa" style="visibility: hidden;" class="row">
  <label>MFA 口令：<input type="number" id="mfa-code"/></label>
</div>
<div class="row">
  <input type="button" value="登录" id="login" onclick="handleLogin()" />
  <input
    type="button"
    value="注册"
    id="register"
    onclick="handleRegister()"
  />
  <input
    type="button"
    value="开启 MFA"
    id="register"
    onclick="handleChangeMFA()"
  />
</div>
<div id="qrcode" class="row"></div>
<div id="qrcode-info" style="visibility: hidden;" class="row">
  请使用 MFA 令牌管理工具扫码添加令牌
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/authing-js-sdk"></script>
```

## 初始化 SDK

**浏览器端初始化时需传入用户池 ID**

* **userPoolId**
  * 用户池 ID，可从 [Authing 控制台](https://authing.cn/dashboard)中[获取](/authing/others/faq.md#ru-he-huo-qu-client-id-he-client-secret)。

```markup
<script>
const auth = new Authing({
	userPoolId: 'your_userpool_id',
});

authing.login({...}).then(info => {})
authing.register({...}).then(info => {})

</script>
```

## 创建一个用户

将此函数绑定到登录按钮的点击事件上，用随机信息注册一个用户。

```javascript
// 使用随机信息注册一个用户
async function handleRegister() {
  let rand = Math.random()
    .toString(36)
    .slice(2);
  let res = await validAuth.register({
    email: rand + "@test.com",
    password: "123456"
  });
  alert(JSON.stringify(res));
  document.getElementById("username").value = res.email;
  document.getElementById("password").value = "123456";
  window.userId = res._id;
}
```

## 开启指定用户的 MFA

操作用户的 MFA 开启状态之前，必须要先获得用户的 token。可以通过 `validAuth.login` 函数以用户的身份登录，或者直接使用用户的 token 初始化。

```javascript
// 开启用户的 MFA
async function handleChangeMFA() {
  let username = document.getElementById("username").value;
  let password = document.getElementById("password").value;
  let verifyCode = document.getElementById("verify-code").value;

  try {
    let res = await validAuth.login({
      email: username,
      password: password,
      verifyCode: verifyCode
    });
    // 或者使用用户的 token 初始化
    // authing.initUserClient('用户的 JWT token');
    let mfa = await validAuth.changeMFA({
      userId: res._id,
      userPoolId: "5c95905578fce5000166f853",
      enable: true
    });
    alert(JSON.stringify(mfa));
    jQuery("#qrcode").qrcode(
      `otpauth://totp/Authing:Test?secret=${mfa.shareKey}&period=30&digits=6&issuer=Authing`
    );
    document.getElementById("qrcode-info").style.visibility = "visible";
  } catch (err) {
    alert("失败消息：" + JSON.stringify(err));
  }
}
```

## 生成 MFA 令牌二维码

社区有许多可以生成二维码的库，这里使用了 jquery 的二维码插件生成 MFA 二维码。

```javascript
jQuery("#qrcode").qrcode(
  `otpauth://totp/Authing:Test?secret=${mfa.shareKey}&period=30&digits=6&issuer=Authing`
);
```

## 处理用户登录逻辑

当调用 `validAuth.login` 函数进行登录时，如果检测到用户开启了 MFA 二次验证，而没有携带 MFA 口令，会抛出一个错误，错误代码为 1635。在错误处理函数中，携带 MFA 口令再次调用 `validAuth.login` 即可。

```javascript
// 登录逻辑
async function handleLogin() {
  // validAuth.login({email: })
  let username = document.getElementById("username").value;
  let password = document.getElementById("password").value;
  let verifyCode = document.getElementById("verify-code").value;
  if (isMFAShow) {
    let MFACode = document.getElementById("mfa-code").value;
    try {
      let res = await validAuth.login({
        email: username,
        password: password,
        MFACode: MFACode,
        verifyCode: verifyCode
      });
      alert("成功消息：" + JSON.stringify(res));
    } catch (err) {
      alert("失败消息：" + JSON.stringify(err));
      if(err.message.code === 2000 || err.message.code === 2001 || err.message.code === 2006) {
        console.log(err.message.data.url)
        document.getElementById("verify-img").src = err.message.data.url
      }
    }
  } else {
    try {
      let res = await validAuth.login({
        email: username,
        password: password,
        verifyCode: verifyCode
      });
      alert("成功消息：" + JSON.stringify(res));
    } catch (err) {
      alert("失败消息：" + JSON.stringify(err));

      if (err.message.code === 1635) {
        showMFAInput();
      }
      if(err.message.code === 2000 || err.message.code === 2001 || err.message.code === 2006) {
        console.log(err.message.data.url)
        document.getElementById("verify-img").src = err.message.data.url
      }
    }
  }
}
```

## 完整代码

Github: [mfa-demo](https://github.com/authing/mfa-demo)

## 运行方法

双击打开 index.html 文件。

或在项目目录启动一个 http 服务器。

```bash
$ npm install -g http-server
$ http-server
```

然后访问 127.0.0.1:8080。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://authing.gitbook.io/authing/mfa/integrate-mfa.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
