Как реализовать asp. net core api external login для SPA? Все учебники, которые я нашел, посвящены MVC и Identity Server. Я хочу создать для этого простое решение.
Я хочу, чтобы приложение SPA перечисляло всех внешних провайдеров, получающих список URL-адресов вызовов из API. Приложение SPA перенаправляет пользователя с помощью windows.location.href
на URL-адрес запроса. с помощью URL-адреса запроса мы также установим URL-адрес перенаправления обратно в приложение SPA. Когда пользователь перенаправляется, он перенаправляет провайдера входа и входа, а затем обратно в бэкэнд-приложение API / MVC. Пользователь будет проверен и снова перенаправлен обратно в приложение SPA с фрагментами ha sh.
BuildRedirectTokenUrl в настоящее время использует строку запроса, которую следует изменить на фрагменты ha sh. То, что я пытаюсь сделать, - это процесс аутентификации посредника. Я все еще хочу использовать тот же конвейер, используя ядро. asp. net. Я видел, как кто-то делал facebook, но потом это пользовательская реализация с перенаправлениями и вызовом.
public abstract class ExternalTokenApiController<TContext, TUser, TRole, TController> : MainApiController<TContext, TUser, TController>
where TContext : DbContext
where TUser : ApplicationUser
where TRole : ApplicationRole
where TController : ControllerBase
{
private readonly IJwtTokenService<TUser> _tokenService;
private readonly SignInManager<TUser> _signInManager;
private readonly RoleManager<TRole> _roleManager;
public static Func<ExternalLogin, TUser> CreateUser { get; set; }
private string[] _redirectionWhiteList = Array.Empty<string>();
/// <summary>
/// Make it only possible to redirect urls set with this method
/// </summary>
/// <param name="urls"></param>
public void SetRedirectionWhiteList(params string[] urls)
{
_redirectionWhiteList = urls;
}
protected ExternalTokenApiController(TContext db, UserManager<TUser> um,
IStringLocalizerFactory localizerFactory,
IJwtTokenService<TUser> tokenService,
RoleManager<TRole> rm,
SignInManager<TUser> sim, ILogger<TController> logger) : base(db, um, localizerFactory, logger)
{
_tokenService = tokenService;
_signInManager = sim;
_roleManager = rm;
}
/// <summary>
/// Begin the challenge, redirecting to the provider.
/// </summary>
/// <param name="provider"></param>
/// <param name="callbackUrl"></param>
/// <param name="returnUrl"></param>
/// <returns></returns>
protected IActionResult OnPost(string provider, string callbackUrl, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = new UriBuilder(callbackUrl);
var query = HttpUtility.ParseQueryString(redirectUrl.Query);
query["returnUrl"] = returnUrl;
redirectUrl.Query = query.ToString();
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl.ToString());
return new ChallengeResult(provider, properties);
}
private string BuildRedirectTokenUrl(string token, string refreshToken, DateTime expires, string returnUrl)
{
var redirectUrl = new UriBuilder(returnUrl);
var query = HttpUtility.ParseQueryString(redirectUrl.Query);
query["token"] = token;
query["refreshToken"] = refreshToken;
query["expires"] = expires.ToString(CultureInfo.InvariantCulture);
redirectUrl.Query = query.ToString();
return redirectUrl.ToString();
}
private bool ValidateUrlFromList(string returnUrl)
{
if(_redirectionWhiteList == null || _redirectionWhiteList.Length == 0)
{
string authority = new Uri(Url.Content("~/")).GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped).ToLower().TrimEnd('/');
_redirectionWhiteList = new string[] { authority };
}
return Utilities.UriUtility.ValidateUrlFromList(returnUrl, _redirectionWhiteList);
}
/// <summary>
/// OnPost should redirect to this method
/// </summary>
/// <param name="isRedirect"></param>
/// <param name="returnUrl"></param>
/// <param name="remoteError"></param>
/// <returns></returns>
protected async Task<IActionResult> OnGetCallbackAsync(bool isRedirect, string returnUrl = null, string remoteError = null)
{
returnUrl ??= Url.Content("~/");
if (remoteError != null)
{
return BadRequest($"Error from external provider: {remoteError}");
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return BadRequest("Error loading external login information.");
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded && info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
var email = info.Principal.Claims
.Where(c => c.Type == ClaimTypes.Email).Select(x => x.Value)
.FirstOrDefault();
var user = await _userManager.FindByEmailAsync(email);
var token = await GenerateTokenAsync(user);
var refreshToken = _tokenService.GenerateRefreshToken(user, this.Request.HttpContext.Connection.RemoteIpAddress);
var repo = new RefreshTokenRepository<TContext>(_db, _localizerFactory);
await repo.SaveTokenAsync(user, refreshToken);
if (isRedirect && ValidateUrlFromList(returnUrl))
{
return Redirect(BuildRedirectTokenUrl(token, refreshToken.Token, refreshToken.Expires, returnUrl));
}
return Ok(new
{
token,
refreshToken = new
{
token = refreshToken.Token,
expires = refreshToken.Expires
},
returnUrl
});
}
// If the user does not have an account, then ask the user to create an account.
if (!result.IsLockedOut && info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
return await OnPostConfirmationAsync(new ExternalLogin() { Info = info, ReturnUrl = returnUrl }, isRedirect);
}
return BadRequest();
}
// Generates a token from the token service and returns it as a string
protected async virtual Task<string> GenerateTokenAsync(TUser user)
{
var claims = await GetValidClaimsAsync(user);
var token = _tokenService.BuildToken(user, claims);
return token;
}
private async Task<IList<Claim>> GetValidClaimsAsync(TUser user)
{
return await IdentityHelper.GetValidClaimsAsync(user, _userManager, _roleManager);
}
private void CreateUserCheck()
{
if (CreateUser == null)
{
throw new ArgumentException(nameof(CreateUser));
}
}
/// <summary>
/// Is not a action, require create user to be defined.
/// </summary>
/// <param name="vm"></param>
/// <param name="isRedirect"></param>
/// <returns></returns>
protected async Task<IActionResult> OnPostConfirmationAsync(ExternalLogin vm, bool isRedirect)
{
CreateUserCheck();
vm.ReturnUrl ??= Url.Content("~/");
if (ModelState.IsValid)
{
var user = CreateUser(vm);
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, vm.Info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", vm.Info.LoginProvider);
var token = await GenerateTokenAsync(user);
var refreshToken = _tokenService.GenerateRefreshToken(user, this.Request.HttpContext.Connection.RemoteIpAddress);
var repo = new RefreshTokenRepository<TContext>(_db, _localizerFactory);
await repo.SaveTokenAsync(user, refreshToken);
if (isRedirect && ValidateUrlFromList(vm.ReturnUrl))
{
return Redirect(BuildRedirectTokenUrl(token, refreshToken.Token, refreshToken.Expires, vm.ReturnUrl));
}
return Ok(new { token, returnUrl = vm.ReturnUrl });
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
var errorMsg = ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).FirstOrDefault();
return BadRequest(errorMsg);
}
}