教程:iOS/macOS 应用中的自助式密码重置

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

本教程介绍如何在无需管理员或支持人员参与的情况下让用户能够更改或重置密码。

在本教程中,你将:

  • 添加自助式密码重置。
  • 处理错误。

先决条件

重置密码

为了重置现有用户的密码,我们需要使用一次性密码 (OTP) 验证电子邮件地址。

  1. 若要验证电子邮件,请使用以下代码片段从 SDK 实例调用 resetPassword(parameters:delegate) 方法:

    let parameters = MSALNativeAuthResetPasswordParameters(username: email)
    nativeAuth.resetPassword(parameters: parameters, delegate: self)
    
  2. 若要将 ResetPasswordStartDelegate 协议实现为类的扩展,请使用以下代码片段:

    extension ViewController: ResetPasswordStartDelegate {
        func onResetPasswordCodeRequired(
            newState: MSAL.ResetPasswordCodeRequiredState,
            sentTo: String,
            channelTargetType: MSALNativeAuthChannelType,
            codeLength: Int
        ) {
            resultTextView.text = "Verification code sent to \(sentTo)"
        }
    
        func onResetPasswordStartError(error: MSAL.ResetPasswordStartError) {
            resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")"
        }
    }
    

    如果调用 resetPassword(parameters:delegate),那么会调用 onResetPasswordCodeRequired()onResetPasswordStartError() 委托方法。

    在最常见的场景中,将调用 onResetPasswordCodeRequired(newState:sentTo:channelTargetType:codeLength) 来指示已发送代码以验证用户的电子邮件地址。 除了发送代码的位置及其所含位数的一些详细信息之外,此委托方法还具有 newState 类型的 ResetPasswordCodeRequiredState 参数,它让我们能够访问两个新方法:

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

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

    newState.submitCode(code: userSuppliedCode, delegate: self)
    
  3. 若要验证所提交的代码,请先使用以下代码片段将 ResetPasswordVerifyCodeDelegate 协议实现为类的扩展:

    extension ViewController: ResetPasswordVerifyCodeDelegate {
    
        func onResetPasswordVerifyCodeError(
            error: MSAL.VerifyCodeError,
            newState: MSAL.ResetPasswordCodeRequiredState?
        ) {
            resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")"
        }
    
        func onPasswordRequired(newState: MSAL.ResetPasswordRequiredState) {
            // use newState instance to submit the new password
        }
    }
    

    在最常见的场景中,我们收到对 onPasswordRequired(newState) 的调用,它指示我们可使用 newState 实例提供新密码。

    newState.submitPassword(password: newPassword, delegate: self)
    
  4. 若要将 ResetPasswordRequiredDelegate 协议实现为类的扩展,请使用以下代码片段:

    extension ViewController: ResetPasswordRequiredDelegate {
    
        func onResetPasswordRequiredError(
            error: MSAL.PasswordRequiredError,
            newState: MSAL.ResetPasswordRequiredState?
        ) {
            resultTextView.text = "Error submitting new password: \(error.errorDescription ?? "no description")"
        }
    
        func onResetPasswordCompleted(newState: SignInAfterResetPasswordState) {
            resultTextView.text = "Password reset completed"
        }
    }
    

    在最常见的场景中,我们收到对 onResetPasswordCompleted(newState) 的调用,它指示密码重置流已完成。

处理错误

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

可通过处理特定错误类型来增强用户体验,如下所示:

func onResetPasswordStartError(error: MSAL.ResetPasswordStartError) {
    if error.isInvalidUsername {
        resultTextView.text = "Invalid username"
    } else if error.isUserNotFound {
        resultTextView.text = "User not found"
    } else if error.isUserDoesNotHavePassword {
        resultTextView.text = "User is not registered with a password"
    } else {
        resultTextView.text = "Error during reset password flow in: \(error.errorDescription ?? "no description")"
    }
}

处理具有状态的错误

某些错误包括对新状态的引用。 例如,如果用户输入的电子邮件验证码不正确,那么错误处理程序将包含对可用于提交新验证码的 ResetPasswordCodeRequiredState 的引用。

在之前的 ResetPasswordVerifyCodeDelegate 协议实现中,我们只是在处理 onResetPasswordError(error:newState) 委托函数时显示错误。

我们可以通过要求用户输入正确的代码并重新提交该代码来改善用户体验,如下所示:

func onResetPasswordVerifyCodeError(
    error: MSAL.VerifyCodeError,
    newState: MSAL.ResetPasswordCodeRequiredState?
) {
    if error.isInvalidCode {
        // Inform the user that the submitted code was incorrect and ask for a new code to be supplied.
        // Request a new code calling `newState.resendCode(delegate)`
        let userSuppliedCode = retrieveNewCode(newState)
        newState?.submitCode(code: userSuppliedCode, delegate: self)
    } else {
        resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")"
    }
}

当用户输入无效密码时,错误处理程序也包含对新状态的引用。 在这种情况下,错误处理程序包括对可用于提交新密码的 ResetPasswordRequiredState 的引用。 下面是一个示例:

func onResetPasswordRequiredError(
    error: MSAL.PasswordRequiredError,
    newState: MSAL.ResetPasswordRequiredState?
) {
    if error.isInvalidPassword {
        // Inform the user that the submitted password was invalid and ask for a new password to be supplied.
        let newPassword = retrieveNewPassword()
        newState?.submitPassword(password: newPassword, delegate: self)
    } else {
        resultTextView.text = "Error submitting password: \(error.errorDescription ?? "no description")"
    }
}

密码重置后登录

SDK 让开发人员能够在重置密码后将用户登录,而无需提供用户名,或者通过一次性密码验证电子邮件地址。

若要在成功重置密码后将用户登录,请从 signIn(parameters:delegate) 函数中返回的新状态 SignInAfterResetPasswordState 中使用 onResetPasswordCompleted(newState) 方法:

extension ViewController: ResetPasswordRequiredDelegate {

    func onResetPasswordRequiredError(
        error: MSAL.PasswordRequiredError,
        newState: MSAL.ResetPasswordRequiredState?
    ) {
        resultTextView.text = "Error submitting new password: \(error.errorDescription ?? "no description")"
    }

    func onResetPasswordCompleted() {
        resultTextView.text = "Password reset completed"
        let parameters = MSALNativeAuthSignInAfterResetPasswordParameters()
        newState.signIn(parameters: parameters, delegate: self)
    }
}

signIn(parameters:delegate) 接受委托参数,我们必须在 SignInAfterResetPasswordDelegate 协议中实现所需的方法。

在最常见的场景中,我们收到对 onSignInCompleted(result) 的调用,它指示用户已登录。 结果可用于检索 access token

extension ViewController: SignInAfterSignUpDelegate {
    func onSignInAfterSignUpError(error: SignInAfterSignUpError) {
        resultTextView.text = "Error signing in after password reset"
    }

    func onSignInCompleted(result: MSAL.MSALNativeAuthUserAccountResult) {
        // User successfully signed in
        let parameters = MSALNativeAuthGetAccessTokenParameters()
        result.getAccessToken(parameters: parameters, delegate: self)
    }
}

getAccessToken(parameters:delegate) 接受委托参数,我们必须在 CredentialsDelegate 协议中实现所需的方法。

在最常见的场景中,我们收到对 onAccessTokenRetrieveCompleted(result) 的调用,它指示用户已获取 access token

extension ViewController: CredentialsDelegate {
    func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) {
        resultTextView.text = "Error retrieving access token"
    }

    func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) {
        resultTextView.text = "Signed in. Access Token: \(result.accessToken)"
    }
}

后续步骤