# 接入 MFA

多因素身份验证(MFA)是一种安全系统,是为了验证一项操作合法性而实行多种身份验证。例如银行的 U 盾,异地登录要求手机短信验证。阅读本教程后,你可以自己为 Authing 的 MFA 二次认证定制化开发登录界面。

# 前期准备

  1. 注册一个 Authing 账号 (opens new window)
  2. 创建一个用户池
  3. 创建一个应用

# API 接口

# 查询用户开启的 MFA 信息

GET
https://core.authing.cn/api/v2/mfa/authenticator

查询用户开启的 MFA 信息

返回用户开启的 MFA 信息

Headers
x-authing-userpool-id
REQUIRED
string

用户池 ID

Authorization
REQUIRED
string

Bearer <用户 Token>

Query Parameters
type
REQUIRED
string

填写 totp

200: OK
{
  "code": 200,
  "message": "获取 MFA Authenticator 成功",
  "data": [
    {
      "id": "5f8eea9b018e1407d2ce7975",
      "createdAt": "2020-10-20T13:48:11.288Z",
      "updatedAt": "2020-10-20T13:48:11.288Z",
      "userId": "5cce4a373ed9f9c9c0fd9596",
      "enable": false,
      "secret": "DMDCO7SNNVGU2VKJ",
      "authenticatorType": "totp",
      "recoveryCode": "10af-4f2f-f34f-f224-d21c-bd16"
    }
  ]
}

没有开启 MFA 时返回:
{
  "code": 200,
  "message": "获取 MFA Authenticator 成功",
  "data": []
}

# 请求绑定 MFA 口令

POST
https://core.authing.cn/api/v2/mfa/totp/associate

获取 MFA 二维码以及 Secret 信息,用于展示,等待用户确认绑定

请求此接口后,用户确认绑定之前,MFA 二次认证不会生效。接口返回 MFA Secret,MFA Uri,MFA 二维码 Data Url,恢复代码。

Headers
x-authing-userpool-id
REQUIRED
string

用户池 ID

Authorization
REQUIRED
string

Bearer <用户 Token>

Form Data Parameters
authenticator_type
REQUIRED
string

填写 totp

200: OK
{
  "code": 200,
  "message": "获取 MFA 密钥成功",
  "data": {
    "authenticator_type": "totp",
    "secret": "JAPDSOAZLV4BG3RA", // MFA Secret 可用于手动添加 MFA
    "qrcode_uri": "otpauth://totp/playground:getstarted%40authing.cn?secret=JAPDSOAZLV4BG3RA&period=30&digits=6&algorithm=SHA1&issuer=playground", // MFA Uri,可用于手动添加 MFA
    // MFA 二维码 Data Url,用于放在 <img> src 中展示二维码
    "qrcode_data_url": "data:image/png;base64,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",
    // 恢复代码
    "recovery_code": "8477-a1a6-662c-a750-bbb4-72a9"
  }
}

# 确认绑定 MFA 口令

POST
https://core.authing.cn/api/v2/mfa/totp/associate/confirm

确认绑定 MFA。

请求此接口后,用户确认绑定 MFA,之后登录会要求输入二次验证 MFA 口令。

Headers
x-authing-userpool-id
REQUIRED
string

用户池 ID

Authorization
REQUIRED
string

Bearer <用户 Token>

Form Data Parameters
authenticator_type
REQUIRED
string

填写 totp

totp
REQUIRED
string

MFA 口令

200: OK
绑定成功
{"code":200,"message":"TOTP MFA 绑定成功"}

绑定失败
{"code":400,"message":"安全码错误,请重新输入"}

# 一次认证后返回 MFA Token

调用 authing-js-sdk 中的登录方法,参考登录。或者直接调用 GraphQL 接口。你需要存储 mfaToken 以备后续使用。

调用 SDK 的处理方式:

try {
  window.user = await window.authing.login({ email, password });
  alert(`登录成功,信息:${JSON.stringify(window.user)}`);
} catch (err) {
  if (err.message.code === 1635) {
    console.log(err.message.data.email);
    console.log(err.message.data.nickname);
    console.log(err.message.data.username);
    console.log(err.message.data.avatar);
    console.log(err.message.data.mfaToken);
    window.mfaToken = err.message.data.mfaToken;
  }
  alert(err.message.message);
}

直接调用 GraphQL 接口的返回信息:

{
  "errors": [
    {
      "message": {
        "code": 1635,
        "message": "请输入二次认证安全码",
        "data": {
          "mfaToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJQb29sSWQiOiI1Y2NlNGFhODNlZDlmOTdiNGRmZDk1ZjAiLCJ1c2VySWQiOiI1ZjhlZTYyY2FmYzJmZmFkMzY0MzQ1YjciLCJhcm4iOiJhcm46Y246YXV0aGluZzo1Y2NlNGFhODNlZDlmOTdiNGRmZDk1ZjA6dXNlcjo1ZjhlZTYyY2FmYzJmZmFkMzY0MzQ1YjciLCJzdGFnZSI6MX0sImlhdCI6MTYwMzIwNjcwOCwiZXhwIjoxNjAzMjA3MDY4fQ.PR7LXqpyH--6sF4eAcOcK1yZBi14lRv_lr9qUtbTQM4",
          "nickname": null,
          "email": "q3@123.com",
          "username": null,
          "avatar": "https://usercontents.authing.cn/authing-avatar.png"
        }
      },
      "locations": [{ "line": 2, "column": 9 }],
      "path": ["login"],
      "extensions": { "code": "INTERNAL_SERVER_ERROR" }
    }
  ],
  "data": { "login": null }
}

# 登录验证 MFA 口令

POST
https://core.authing.cn/api/v2/mfa/totp/verify

用于登录时一次认证成功后,检验二次认证口令是否正确。

对于开启二次认证的用户,第一次认证成功后会返回一个 mfaToken,需要携带 mfaToken 请求本接口完成二次认证

Headers
x-authing-userpool-id
REQUIRED
string

用户池 ID

Authorization
REQUIRED
string

Bearer <用户 mfaToken>

Form Data Parameters
totp
REQUIRED
string

MFA 口令

200: OK
登录成功
{
    "code": 200,
    "message": "二次验证成功",
    "data": {
        "thirdPartyIdentity": {
            "provider": null,
            "refreshToken": null,
            "accessToken": null,
            "scope": null,
            "expiresIn": null,
            "updatedAt": null
        },
        "id": "5f8ee62cafc2ffad364345b7",
        "createdAt": "2020-10-20T13:29:16.896Z",
        "updatedAt": "2020-10-20T14:54:07.301Z",
        "userPoolId": "5cce4aa83ed9f97b4dfd95f0",
        "isRoot": false,
        "oauth": null,
        "email": "q3@123.com",
        "phone": null,
        "username": null,
        "unionid": null,
        "openid": null,
        "nickname": null,
        "company": null,
        "photo": "https://usercontents.authing.cn/authing-avatar.png",
        "browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36",
        "device": null,
        "password": "76847018c664261747924735403ee0a5",
        "salt": "20k8b1318gie",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJQb29sSWQiOiI1Y2NlNGFhODNlZDlmOTdiNGRmZDk1ZjAiLCJhcHBJZCI6bnVsbCwidXNlcklkIjoiNWY4ZWU2MmNhZmMyZmZhZDM2NDM0NWI3IiwiYXJuIjoiYXJuOmNuOmF1dGhpbmc6NWNjZTRhYTgzZWQ5Zjk3YjRkZmQ5NWYwOnVzZXI6NWY4ZWU2MmNhZmMyZmZhZDM2NDM0NWI3IiwiaWQiOiI1ZjhlZTYyY2FmYzJmZmFkMzY0MzQ1YjciLCJfaWQiOiI1ZjhlZTYyY2FmYzJmZmFkMzY0MzQ1YjciLCJwaG9uZSI6bnVsbCwiZW1haWwiOiJxM0AxMjMuY29tIiwidXNlcm5hbWUiOm51bGwsInVuaW9uaWQiOm51bGwsIm9wZW5pZCI6bnVsbH0sImlhdCI6MTYwMzIwNTY0NywiZXhwIjoxNjA0NTAxNjQ3fQ.U1NmmdOydZ-D_yzhQizpZ--Z5hgzSlZbWxKn3e7BYDQ",
        "tokenExpiredAt": "2020-11-04T14:54:07.287Z",
        "loginsCount": 24,
        "lastIp": "124.204.56.98",
        "name": null,
        "givenName": null,
        "familyName": null,
        "middleName": null,
        "profile": null,
        "preferredUsername": null,
        "website": null,
        "gender": "U",
        "birthdate": null,
        "zoneinfo": null,
        "locale": null,
        "address": null,
        "formatted": null,
        "streetAddress": null,
        "locality": null,
        "region": null,
        "postalCode": null,
        "city": null,
        "province": null,
        "country": null,
        "registerSource": [
            "basic:email"
        ],
        "emailVerified": false,
        "phoneVerified": false,
        "lastLogin": "2020-10-20T14:54:07.298Z",
        "blocked": false,
        "isDeleted": false,
        "sendSmsCount": 0,
        "sendSmsLimitCount": 1000,
        "identities": []
    }
}

口令错误
{"code":6001,"message":"安全码错误,请重新输入"}

# 使用恢复代码

POST
https://core.authing.cn/api/v2/mfa/totp/recovery

用于用户登录一次认证成功后,丢失 MFA 口令时恢复账号访问。

如果用户开启了二次认证而丢失了 MFA 口令,需要使用恢复代码来恢复账号的访问。使用恢复代码等效于使用 MFA 口令,使用过后会为用户生成新的恢复代码。用户可以在登录后解绑 MFA 并重新绑定新的 MFA。

Headers
x-authing-userpool-id
REQUIRED
string

用户池 ID

Authorization
REQUIRED
string

Bearer <用户 mfaToken>

Form Data Parameters
recoveryCode
REQUIRED
string

恢复代码,在绑定 MFA 口令时返回的

200: OK
登录成功
{
    "code": 200,
    "message": "二次验证成功",
    "data": {
        "thirdPartyIdentity": {
            "provider": null,
            "refreshToken": null,
            "accessToken": null,
            "scope": null,
            "expiresIn": null,
            "updatedAt": null
        },
        "id": "5f8ee62cafc2ffad364345b7",
        "createdAt": "2020-10-20T13:29:16.896Z",
        "updatedAt": "2020-10-20T14:54:07.301Z",
        "userPoolId": "5cce4aa83ed9f97b4dfd95f0",
        "isRoot": false,
        "oauth": null,
        "email": "q3@123.com",
        "phone": null,
        "username": null,
        "unionid": null,
        "openid": null,
        "nickname": null,
        "company": null,
        "photo": "https://usercontents.authing.cn/authing-avatar.png",
        "browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36",
        "device": null,
        "password": "76847018c664261747924735403ee0a5",
        "salt": "20k8b1318gie",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJQb29sSWQiOiI1Y2NlNGFhODNlZDlmOTdiNGRmZDk1ZjAiLCJhcHBJZCI6bnVsbCwidXNlcklkIjoiNWY4ZWU2MmNhZmMyZmZhZDM2NDM0NWI3IiwiYXJuIjoiYXJuOmNuOmF1dGhpbmc6NWNjZTRhYTgzZWQ5Zjk3YjRkZmQ5NWYwOnVzZXI6NWY4ZWU2MmNhZmMyZmZhZDM2NDM0NWI3IiwiaWQiOiI1ZjhlZTYyY2FmYzJmZmFkMzY0MzQ1YjciLCJfaWQiOiI1ZjhlZTYyY2FmYzJmZmFkMzY0MzQ1YjciLCJwaG9uZSI6bnVsbCwiZW1haWwiOiJxM0AxMjMuY29tIiwidXNlcm5hbWUiOm51bGwsInVuaW9uaWQiOm51bGwsIm9wZW5pZCI6bnVsbH0sImlhdCI6MTYwMzIwNTY0NywiZXhwIjoxNjA0NTAxNjQ3fQ.U1NmmdOydZ-D_yzhQizpZ--Z5hgzSlZbWxKn3e7BYDQ",
        "tokenExpiredAt": "2020-11-04T14:54:07.287Z",
        "loginsCount": 24,
        "lastIp": "124.204.56.98",
        "name": null,
        "givenName": null,
        "familyName": null,
        "middleName": null,
        "profile": null,
        "preferredUsername": null,
        "website": null,
        "gender": "U",
        "birthdate": null,
        "zoneinfo": null,
        "locale": null,
        "address": null,
        "formatted": null,
        "streetAddress": null,
        "locality": null,
        "region": null,
        "postalCode": null,
        "city": null,
        "province": null,
        "country": null,
        "registerSource": [
            "basic:email"
        ],
        "emailVerified": false,
        "phoneVerified": false,
        "lastLogin": "2020-10-20T14:54:07.298Z",
        "blocked": false,
        "isDeleted": false,
        "sendSmsCount": 0,
        "sendSmsLimitCount": 1000,
        "identities": []
    },
    "recoveryCode": "9225-be3f-4646-fa3a-7a32-a098"
}

口令错误
{"code":6002,"message":"恢复代码错误,请重新输入"}

# MFA Demo

Github: mfa-demo (opens new window)

# 运行方法

双击打开 index.html 文件。

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

$ npm install -g http-server
$ http-server

然后访问 127.0.0.1:8080。