如何使用 SPA 处理 ASP.NET webapi 中的密码重置令牌流

How to handle password reset token flow in ASP.NET webapi with SPA

我有一个 API 是 ASP.NET webapi2。不是.NET 核心。然后我有一个 React SPA。我正在使用身份和 Oauth2。

我正在实施身份验证系统,但密码重置流程让我有些困惑。 API 将生成一个通过电子邮件发送给客户端的令牌。客户端然后单击 link 导航到某处。

link 导航到客户端 javascript 应用程序是有意义的,该应用程序然后从令牌中获取参数并将它们提交给 API。这样做的问题是 API 必须知道客户端 url 才能生成 link。我不希望 API 知道有关客户端应用程序所在位置的任何信息,因为这看起来像是愚蠢的耦合。

另一个选项是密码重置 link 直接导航到 API 然后将用户重定向到客户端应用程序。这与 API 需要知道客户端在哪里并且它也有这个讨厌的重定向 hack 有同样的问题。

是否有这方面的资源或关于它应该如何工作的建议?

谢谢

我相信您正在寻找的是身份提供者的 "login" 门户。密码重置流程是身份提供者的一部分,而不是您的主要应用程序。因此,当您的用户单击他们的密码重置时,它会将他们发送到身份提供者密码重置页面(此重置密码 link 可能根本不应该存在于您的主应用程序中)。重置后,用户将登录并使用授权令牌重定向到您的应用程序,您将使用该授权令牌交换访问令牌。必须为每个想要使用您的身份提供者的应用程序配置重定向。

WebAPI 没有在 AccountController 中公开所需的方法。

我使用添加到 AccountController 的这 2 个方法

[Route("ForgotPassword")]
public async Task<IHttpActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = await UserManager.FindByNameAsync(model.UserName) ?? await UserManager.FindByEmailAsync(model.UserName);

    if (user == null)
    {
        return Ok("Ok");
    }


    if (user.Email == null)
    {
        throw new InvalidOperationException("Cannot send email. Email address not configured.");
    }

    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);

#if DEBUG
    System.Diagnostics.Process.Start(
        string.Format(
            "http://localhost:4444/#/forgot-password-reset/{0}/{1}",
            HttpUtility.UrlEncode(user.UserName),
            HttpUtility.UrlEncode(token)
        )
    );
#endif

    SendMailForgotPassword(user, token);

    return Ok("Ok");
}

[Route("ForgotPasswordReset")]
public async Task<IHttpActionResult> ForgotPasswordReset(ForgotPasswordResetViewModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = await UserManager.FindByNameAsync(model.UserName) ?? await UserManager.FindByEmailAsync(model.UserName);
    if (user == null)
    {
        return Ok("Ok");
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);
    if (result.Succeeded)
    {
        return Ok("Ok");
    }

    throw new InvalidOperationException(string.Join("\r\n", result.Errors));
}