教程:使用本机身份验证在 iOS/macOS 应用中添加注册

适用于:带灰色 X 号的白色圆圈。 员工租户 带白色勾号的绿色圆圈。 外部租户(了解详细信息

本教程演示如何使用电子邮件一次性密码或用户名(电子邮件)和密码注册用户,以及如何使用本机身份验证在 iOS/macOS 应用中收集用户属性。

  • 使用电子邮件一次性密码或用户名(电子邮件)和密码注册用户。
  • 在注册期间收集用户属性。
  • 处理注册错误。

先决条件

注册用户

若要使用电子邮件一次性密码或用户名(电子邮件)和密码注册用户,请从用户那里收集电子邮件,然后向用户发送包含电子邮件一次性密码的电子邮件。 用户输入有效的电子邮件一次性密码以验证其用户名。

若要注册用户,需要:

  1. 创建用户界面 (UI) 以:

    • 从用户处收集电子邮件。 向输入中添加验证,以确保用户输入有效的电子邮件地址。
    • 如果使用用户名(电子邮件)和密码注册,请收集密码。
    • 从用户处收集电子邮件一次性密码。
    • 如果需要,收集用户属性。
    • 如果用户未收到密码,则重新发送一次性密码。
    • 开始注册流程。
  2. 在应用中添加一个按钮,其选择事件会触发以下代码片段:

    @IBAction func signUpPressed(_: Any) {
        guard let email = emailTextField.text else {
            resultTextView.text = "Email or password not set"
            return
        }
    
        let parameters = MSALNativeAuthSignUpParameters(username: email)
        nativeAuth.signUp(parameters: parameters, delegate: self)
    }
    
    • 为使用电子邮件一次性密码进行用户注册,将使用库的 signUp(parameters:delegate) 方法,该方法通过针对传递的委托对象调用某一个方法(必须实现 SignUpStartDelegate 协议)来进行异步响应。 以下代码行启动用户注册过程:

      nativeAuth.signUp(parameters: parameters, delegate: self)
      

      signUp(parameters:delegate) 方法中,我们将传递一个 MSALNativeAuthSignUpParameters 实例,其中包含提交表单中用户的电子邮件地址以及委托(实现 SignUpStartDelegate 协议的类)。

    • 要使用电子邮件与密码来注册用户,请使用以下代码片段:

      @IBAction func signUpPressed(_: Any) {
          guard let email = emailTextField.text, let password = passwordTextField.text else {
             resultTextView.text = "Email or password not set"
             return
          }
      
          let parameters = MSALNativeAuthSignUpParameters(username: email)
          parameters.password = password
          nativeAuth.signUp(parameters: parameters, delegate: self)
      }
      

      我们使用库的 signUp(parameters:delegate) 方法,该方法通过对传递的委托对象调用其中一个方法(必须实现 SignUpStartDelegate 协议)来进行异步响应。 以下代码行启动用户注册过程:

      nativeAuth.signUp(parameters: parameters, delegate: self)
      

      signUp(parameters:delegate) 方法中,我们将传递一个 MSALNativeAuthSignUpParameters 实例,其中包含用户的电子邮件地址及其密码以及委托(实现 SignUpStartDelegate 协议的类)。

    • 若要将 SignUpStartDelegate 协议实现为类的扩展,请使用:

      extension ViewController: SignUpStartDelegate {
          func onSignUpStartError(error: MSAL.SignUpStartError) {
              resultTextView.text = "Error signing up: \(error.errorDescription ?? "no description")"
          }
      
          func onSignUpCodeRequired(
              newState: MSAL.SignUpCodeRequiredState,
              sentTo: String,
              channelTargetType: MSAL.MSALNativeAuthChannelType,
              codeLength: Int
          ) {
              resultTextView.text = "Verification code sent to \(sentTo)"
          }
      }
      

      如果调用 signUp(parameters:delegate),那么会调用 onSignUpCodeRequired()onSignUpStartError() 委托方法。 调用 onSignUpCodeRequired(newState:sentTo:channelTargetType:codeLength) 来指示已发送代码以验证用户的电子邮件地址。 除了发送代码的位置及其所含位数的一些详细信息之外,此委托方法还具有 newState 类型的 SignUpCodeRequiredState 参数,它让我们能够访问两个新方法:

      • submitCode(code:delegate)
      • resendCode(delegate)

      若要提交用户提供给我们的代码,请使用:

      newState.submitCode(code: userSuppliedCode, delegate: self)
      
      • 若要将 SignUpVerifyCodeDelegate 协议实现为类的扩展,请使用:

        extension ViewController: SignUpVerifyCodeDelegate {
            func onSignUpVerifyCodeError(error: MSAL.VerifyCodeError, newState: MSAL.SignUpCodeRequiredState?) {
                resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")"
            }
        
            func onSignUpCompleted(newState: SignInAfterSignUpState) {
                resultTextView.text = "Signed up successfully!"
            }
        }
        

        submitCode(code:delegate) 接受委托参数,我们必须在 SignUpVerifyCodeDelegate 协议中实现所需的方法。 在最常见的场景中,会收到对 onSignUpCompleted(newState) 的调用,它指示用户已注册且流已完成。

在注册期间收集用户属性

无论是使用电子邮件一次性密码还是用户名(电子邮件)和密码注册用户,都可以在创建用户帐户之前收集用户属性。 可以使用具有属性属性的 signUp(parameters:delegate) 调用 MSALNativeAuthSignUpParameters 方法。

  1. 使用以下代码片段收集用户特性:

    let attributes = [
        "country": "United States",
        "city": "Redmond"
    ]
    
    let parameters = MSALNativeAuthSignUpParameters(username: email)
    parameters.password = password
    parameters.attributes = attributes
    nativeAuth.signUp(parameters: parameters, delegate: self)
    

    signUp(parameters:delegate) 会导致调用 onSignUpCodeRequired()onSignUpStartError() 委托方法,或者调用 onSignUpAttributesInvalid(attributeNames: [String])(如果它已在委托中实现)。

  2. 若要将 SignUpStartDelegate 协议实现为类的扩展,请使用以下代码片段:

    extension ViewController: SignUpStartDelegate {
        func onSignUpStartError(error: MSAL.SignUpStartError) {
            resultTextView.text = "Error signing up: \(error.errorDescription ?? "no description")"
        }
    
        func onSignUpCodeRequired(
            newState: MSAL.SignUpCodeRequiredState,
            sentTo: String,
            channelTargetType: MSAL.MSALNativeAuthChannelType,
            codeLength: Int
        ) {
            resultTextView.text = "Verification code sent to \(sentTo)"
        }
    
        func onSignUpAttributesInvalid(attributeNames: [String]) {
           resultTextView.text = "Invalid attributes  \(attributeNames)"
        }
    }
    

    如果属性无效,则调用方法 onSignUpAttributesInvalid(attributeNames: [String])。 在这种情况下,我们将向用户显示无效属性的列表。 否则,将调用 onSignUpCodeRequired(newState:sentTo:channelTargetType:codeLength) 来指示已发送代码以验证用户的电子邮件地址。 除了代码接收者和代码位数等详细信息外,此委托方法还具有类型为 newStateSignUpCodeRequiredState 参数,这可实现对两个新方法的访问:

    • submitCode(code:delegate)
    • resendCode(delegate)

用户属性存在于一个或多个页面中

若要跨一个或多个页面分布属性,必须在客户标识和访问管理 (CIAM) 租户配置中,将要跨不同页面收集的属性设置为强制属性。

调用 signUp(parameters:delegate),而不传递 MSALNativeAuthSignUpParameters 实例中的任何属性。 下一步是调用 newState.submitCode(code: userSuppliedCode, delegate: self) 来验证用户的电子邮件。

我们像以前一样将 SignUpVerifyCodeDelegate 协议实现为类的扩展,但这次除了必需的方法外,还必须实现可选的方法 onSignUpAttributesRequired(attributes:newState)

extension ViewController: SignUpVerifyCodeDelegate {
    func onSignUpAttributesRequired(newState: SignUpAttributesRequiredState) {
        resultTextView.text = "Attributes required"
    }

    func onSignUpVerifyCodeError(error: MSAL.VerifyCodeError, newState: MSAL.SignUpCodeRequiredState?) {
        resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")"
    }

    func onSignUpCompleted(newState: SignInAfterSignUpState) {
        resultTextView.text = "Signed up successfully!"
    }
}

此委托方法具有类型为 newStateSignUpAttributesRequiredState 参数,它使我们能够访问新方法:

  • submitAttributes(attributes:delegate)

若要提交用户提供的属性,请使用以下代码片段:

let attributes = [
    "country": "United States",
    "city": "Redmond"
]

newState.submitAttributes(attributes: attributes, delegate: self)

我们还将把 SignUpAttributesRequiredDelegate 协议作为类的扩展来实现。

extension ViewController: SignUpAttributesRequiredDelegate {
    func onSignUpAttributesRequiredError(error: AttributesRequiredError) {
        resultTextView.text = "Error submitting attributes: \(error.errorDescription ?? "no description")"
    }

    func onSignUpAttributesRequired(attributes: [MSALNativeAuthRequiredAttribute], newState: SignUpAttributesRequiredState) {
        resultTextView.text = "Attributes required"
    }

    func onSignUpAttributesInvalid(attributeNames: [String], newState: SignUpAttributesRequiredState) {
        resultTextView.text = "Attributes invalid"
    }

    func onSignUpCompleted(newState: SignInAfterSignUpState) {
        resultTextView.text = "Signed up successfully!"
    }
}

如果用户未提供所有必需的属性或属性无效,则会调用以下委托方法:

  • onSignUpAttributesInvalid:指示发送的一个或多个属性未通过输入验证。 此错误包含 attributeNames 参数,该参数是开发人员发送的、未通过输入验证的所有属性的列表。
  • onSignUpAttributesRequired:指示服务器需要先发送一个或多个属性,然后才能创建用户帐户。 在租户配置中将一个或多个属性设置为强制属性时,就会发生这种情况。 此结果包含 attributes 参数,它是一个 MSALNativeAuthRequiredAttribute 对象列表,其中概述了有关 API 所需用户属性的详细信息。

这两个委托方法都包含新的状态引用。 使用 newState 参数再次通过新属性来调用 submitAttributes(attributes:delegate)

处理注册错误

注册期间,并非每个操作都能成功。 例如,用户可能会尝试使用已使用的电子邮件地址进行注册或提交无效代码。

在之前的 SignUpStartDelegate 协议实现中,我们在处理 onSignUpStartError(error) 委托函数时简单地显示了错误。

若要通过管理特定错误类型来增强用户体验,请使用以下代码片段:

func onSignUpStartError(error: MSAL.SignUpStartError) {
    if error.isUserAlreadyExists {
        resultTextView.text = "Unable to sign up: User already exists"
    } else if error.isInvalidPassword {
        resultTextView.text = "Unable to sign up: The password is invalid"
    } else if error.isInvalidUsername {
        resultTextView.text = "Unable to sign up: The username is invalid"
    } else {
        resultTextView.text = "Unexpected error signing up: \(error.errorDescription ?? "no description")"
    }
}

可选:注册流程完成后登录

注册流成功后,你可以登录用户,而无需启动登录流。 有关详细信息,请参阅教程:注册 iOS/macOS 应用后自动登录用户文章。

后续步骤