Как реализовать внешний логин asp. net core api для SPA? - PullRequest
0 голосов
/ 31 января 2020

Как реализовать 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);
    }
}
...