Как обработать поток токенов сброса пароля в ASP.NET webapi с SPA
У меня есть API, который является ASP.NET webapi2. Не.NET Core. Тогда у меня есть React SPA. Я использую Identity и Oauth2.
Я внедряю систему аутентификации, и поток сброса пароля меня немного озадачил. API сгенерирует токен, который будет отправлен клиенту по электронной почте. Затем клиент нажимает на ссылку, чтобы перейти куда-нибудь.
Ссылка имеет смысл перейти к клиентскому приложению javascript, которое затем берет параметры из токена и передает их в API. Проблема заключается в том, что URL-адрес клиента должен быть известен API, чтобы иметь возможность генерировать ссылку. Я не хочу, чтобы API знал что-либо о том, где находятся клиентские приложения, потому что это похоже на глупую связь.
Другой вариант - ссылка для сброса пароля, которая ведет непосредственно к API, где она перенаправляет пользователя в клиентское приложение. Это та же проблема, что API должен знать, где находится клиент, и у него также есть этот неприятный хак перенаправления.
Есть ли на это ресурсы или предложения о том, как это должно работать?
Спасибо
2 ответа
Я полагаю, что вы ищете портал авторизации провайдера идентификации. Поток для сброса пароля является частью поставщика удостоверений, а не вашего основного приложения. Поэтому, когда ваши пользователи нажимают на кнопку сброса пароля, они отправляют их на страницу сброса пароля провайдера идентификации (эта ссылка для сброса пароля, вероятно, вообще не должна существовать в вашем основном приложении). После сброса пользователь войдет в систему и будет перенаправлен в ваше приложение с токеном авторизации, который вы обменяете на токен доступа. Перенаправление должно быть настроено для каждого приложения, которое хочет использовать ваш поставщик удостоверений.
WebAPI не предоставляет необходимые методы в AccountController.
Я использую эти 2 метода, которые я добавил в AccountController
[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));
}