Error loading external login information

I'm trying to get IdentityServer4 get to work with ASP.NET Core Identity using my own UserStore for SSO. While the guides seem rather straightforward, and the authentication process itself seems to...

I’m trying to get IdentityServer4 get to work with ASP.NET Core Identity using my own UserStore for SSO. While the guides seem rather straightforward, and the authentication process itself seems to work, in the application (another ASP.NET Core MVC application) I get the following error:

Error loading external login information

My setup is as follows:

For the ASP.NET MVC application (the client):

services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
    options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
    options.ExpireTimeSpan =TimeSpan.FromMinutes(30);
})
.AddOpenIdConnect("oidc", options =>
{
    options.SignInScheme = "Cookies";

    options.Authority = "https://localhost:5001/";

    options.ClientId = "clientId";
    options.ClientSecret = "secret";
    options.SaveTokens = true;
    options.ResponseType = "code id_token";
    options.Scope.Add(IdentityServerConstants.StandardScopes.Profile);
    options.Scope.Add(IdentityServerConstants.StandardScopes.Email);
    options.Scope.Add(IdentityServerConstants.StandardScopes.OfflineAccess);

    options.GetClaimsFromUserInfoEndpoint = true;
});

For the IdentityServer4 application:

services.AddScoped<UserManager<User>, MyUserManager>();
services.AddIdentity<User, UserGroup>()
    .AddRoleStore<MyRoleStore>()
    .AddUserStore<MyUserStore>()
    .AddDefaultTokenProviders();

services.Configure<IdentityOptions>(options =>
{
    options.ClaimsIdentity.UserIdClaimType = JwtClaimTypes.Subject;
    options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.Name;
    options.ClaimsIdentity.RoleClaimType = JwtClaimTypes.Role;
});

services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryApiResources(OpenIDConfig.GetApiResources())
    .AddInMemoryIdentityResources(OpenIDConfig.GetIdentityResources())
    .AddInMemoryClients(OpenIDConfig.GetClients())
    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator<User>>()
    .AddProfileService<ProfileService<User>>();

The main issue is that I don’t know where to even start looking for why there is a problem with this after a successful authentication flow.

Я использую ядро Asp.net для разработки веб-приложения с помощью Google Authentication Login

Мне удалось получить учетные данные Google и сначала зарегистрировать пользователя. Но проблема заключается даже в том, что пользователь регистрируется в приложении с помощью Google Credentials, он всегда запрашивает регистрацию снова и снова для приложения.

Я обнаружил, что это происходит из-за вызова функции ExternalLoginSignInAsync, и это всегда дает ложь для этого, я изменял эти параметры и пробовал несколько раз isPersistent: false / true) и bypassTwoFactor: true / false. У меня тест выше всех вариантов. Но это всегда дает ложный результат. Как и я пытался с Google Authentication с обычным зарегистрированным пользователем login.it также дал мне тот же результат

   public async Task<IActionResult> OnGetCallbackAsync(string returnUrl =null,string remoteError = null)
{
            returnUrl = returnUrl ?? Url.Content("~/");
            if (remoteError != null)
            {
                ErrorMessage = $"Error from external provider: 
                {remoteError}";
                return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
            }
            var info = await _signInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                ErrorMessage = "Error loading external login information.";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }
            var result = await 
               _signInManager.ExternalLoginSignInAsync(info.LoginProvider, 
               info.ProviderKey, isPersistent:false, bypassTwoFactor : true);

            if (result.Succeeded)
            {
                _logger.LogInformation("{Name} logged in with {LoginProvider} 
                provider.", info.Principal.Identity.Name, 
                info.LoginProvider);
                // return LocalRedirect(returnUrl);
                return LocalRedirect("/Customer/ProjectsApiKey/");
            }    

Не могли бы вы, пожалуйста, кому-нибудь, кто уже решил эту проблему, эта проблема поможет мне. Я ожидаю, что я должен сделать для проверки пользователя, уже зарегистрированного или нет с Google Authentication

2 ответа

Лучший ответ

Наконец, я нашел ошибку, это произошло из-за подтверждения электронной почты. В своем классе startup.cs я добавил

 config.SignIn.RequireConfirmedEmail = true;


public void ConfigureServices(IServiceCollection services)
{

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddDefaultIdentity<IdentityUser>(config =>
    {
        config.SignIn.RequireConfirmedEmail = true;
    })
        .AddDefaultUI(UIFramework.Bootstrap4)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    // requires
    // using Microsoft.AspNetCore.Identity.UI.Services;
    // using WebPWrecover.Services;
    services.AddTransient<IEmailSender, EmailSender>();
    services.Configure<AuthMessageSenderOptions>(Configuration);

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

В связи с этим всегда требуется подтверждать адрес электронной почты для входа в систему пользователя, даже если мы используем Google Authentication. Чтобы исправить это, я сделал, когда создавал нового пользователя с помощью входа в систему Google Authentication, я установил параметры пользователя, включая EmailConfirmation = true, добавление к имени пользователя & Пользователь Email. Затем эта ошибка была исправлена. после этого, когда я вызываю функцию ниже

var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            // Get the information about the user from the external login provider
            var info = await _signInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                ErrorMessage = "Error loading external login information during confirmation.";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }

            if (ModelState.IsValid)
            {
                // var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };   comment dulith
                var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email ,EmailConfirmed=true};
                var result = await _userManager.CreateAsync(user);
                await _userManager.AddToRoleAsync(user, SD.User);
                if (result.Succeeded)
                {

                    result = await _userManager.AddLoginAsync(user, info);
                    if (result.Succeeded)
                    {
                        await _signInManager.SignInAsync(user, isPersistent: false);
                        _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
                       // return LocalRedirect(returnUrl);
                        return LocalRedirect("/Customer/ProjectsApiKey/");
                    }
                }

Если пользователь существует

result.Succeeded is true

А также большое спасибо за предыдущий ответ.


1

Dulith chinthaka
7 Окт 2019 в 05:26

Когда GetExternalLoginInfoAsync возвращает false, вы можете проверить, существует ли пользователь, если он существует, добавьте логин.

В противном случае пользователь не имеет учетной записи, а затем просит пользователя создать учетную запись.

    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
    if (result.Succeeded)
    {
        return RedirectToAction("Index", "Home");
    }
    if (result.IsLockedOut)
    {
        return RedirectToAction(nameof(Lockout));
    }
    else
    {
        var user = await _userManager.FindByEmailAsync(email);
        if (user != null)
        {
            var resultTemp = await _userManager.AddLoginAsync(user, info);
            if (resultTemp.Succeeded)
            {
                await _signInManager.SignInAsync(user, isPersistent: true);
                return RedirectToAction("Index", "Home");
            }
        }
        // User does not have an account, then ask the user to create an account.
        ViewData["ReturnUrl"] = returnUrl;
        ViewData["LoginProvider"] = info.LoginProvider;
        var email = info.Principal.FindFirstValue(ClaimTypes.Email);
        return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
    }


1

Gökten Karadağ
4 Окт 2019 в 06:23

#c# #.net-core #entity-framework-core #asp.net-identity #identityserver4

#c# #.net-ядро #сущность-структура-ядро #asp.net-идентификация #identityserver4

Вопрос:

Я использую комбинацию IdentityServer4 и идентификатора ядра ASP .NET для создания страницы федеративного входа. Одним из внешних поставщиков, которых я использую, является Azure Active Directory через протокол Open ID Connect. Я также интегрировал EntityFrameworkCore для всего моего хранилища данных.

После того, как я создал свой первоначальный веб-сайт с помощью аутентификации личности, я добавляю соответствующие службы в свой Startup.cs файл.

 services.AddOidcStateDataFormatterCache();
// Some DI registrations
services.Configure<CookiePolicyOptions>(options =>
{
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<MyDbContext>();
services.AddMvc().AddRazorPages(options => /* Some options */);
services.AddIdentityServer(options =>
    options.Events.RaiseErrorEvents = true;
    options.Events.RaiseFailureEvents = true;
    options.Events.RaiseInformationEvents = true;
    options.Events.RaiseSuccessEvents = true;
)
.AddConfigurationStore(options => /* Set up DbContext */)
.AddOperationStore(options => /* Set up DbContext */)
.AddAspNetIdentity<MyAppUser>();
services.AddAuthentication().AddOpenIdConnect(options =>
{
    // Does not bind itself for mysterious reasons
    Configuration.GetSection("OpenIdConect").Bind(options)
});
 

Я решил, что это будет выглядеть лучше, если я выполню большую часть настройки аутентификации в своем appsettings.json файле.

 {
    "OpenIdConnect": {
        "ClientId": "<my_client_id>",
        "Authority:" "https://login.microsoft.com/<my_tenant_id>",
        "PostLogoutRedirectUri": "http://localhost:5000/MyApp/Account",
        "CallbackPath": "/signin-oidc",
        "ResponseType": "code id_token",
        "Scope": "openid profile email",
        "SignInScheme": "idsrv.external",
        "AutomaticAuthenticate": "false",
        "AutomaticChallenge": "false",
        "RequireHttpsMetadata": "true"
     }
}
 

Все запускается, и мой поставщик входа в систему Open ID Connect автоматически появляется на странице входа в систему. Потрясающе! При попытке использовать это, я получаю сообщение об ошибке: Error loading external login information. заканчивается тем, что на странице Razor со скаффолдингом
Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs:72 есть этот фрагмент кода:

 var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
    ErrorMessage = "Error loading external login information.";
    return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
 

info всегда разрешается в null. Я подозреваю, что это проблема с конфигурацией где-то в моем Startup.cs , или какая-то магия, о которой не удосужились упомянуть в документации.

Ответ №1:

После прочтения моих мозгов я решил перечитать документацию IdentityServer4 о внешних поставщиках удостоверений.

Это привлекло мой (усталый) взгляд:

Учитывая, что это такая распространенная практика, IdentityServer регистрирует обработчик файлов cookie специально для этого рабочего процесса внешнего поставщика. Схема представлена через IdentityServerConstants.ExternalCookieAuthenticationScheme константу.

Если бы вы использовали наш внешний обработчик файлов cookie, то для SignInScheme [в вашей настройке службы OpenIdConnect] вы бы присвоили значение IdentityServerConstants.ExternalCookieAuthenticationScheme константе

Ну, я думаю, что я не использую их внешний обработчик файлов cookie. Я уверен, что я позволяю ASP .NET Core Identity делать это. Я решил уничтожить эту строку в своем appsettings.json:

 "SignInScheme": "idsrv.external"
 

И вот, я мог войти в систему, используя свои учетные данные Active Directory. Читая фактический исходный код GetExternalLoginInfoAsync , я увидел, что они искали очень конкретный файл cookie, а его не "idsrc.external" было . Оставить его по умолчанию было тем, что мне было нужно.

You can easily implement external authentication providers to allow the users of your Blazor application to log in with Facebook, Google, Microsoft, Twitter, and many others. However, you have to write code to associate an external login to an existing login. You can implement several methods such as sending a code in an email.

The method, covered here, will ask the user to enter the password of the existing account to make the association.

image

After clicking the Log In link to access the login screen, the user is presented with the option to log in using one or more external authentication providers.

Clicking the button for the external authentication provider, will direct the user to the authentication provider, to be authenticated.

image

After authenticating with the external authentication provider, the user is returned to the application.

If the user already has an account in the application they will be logged in. If the user does not have an account in the application, they will be presented with the Register page.

The email of the external account is displayed (they will not be allowed to change it) and they will be presented with the Register button that will create the account.

image

If the email address is already being used by an existing account, the user is informed of this, and they are prompted to enter the password for the existing account to associate this external login with that account.

image

If they do not enter the correct password, they are prompted again.

image

After entering the correct password, they are logged in, and the external login is associated with the existing account.

The user can now log in directly using a username and password, or log into the same account using the external authentication provider.

image

Note: When an account is created only using an external authentication provider, it will not have a password in the database. If a user attempts to associate an external provider to that account, (for example to associate a Facebook login to an account that was created using a Google login), they will see an error message.

image

To resolve this, the user simply needs to log in, using the working external authentication provider, and click on their username.

image

On the manage account settings page, they can select the Password menu option, to be presented with the form that will allow them to set a password for the account.

Creating The Application

image

We start with the application from the tutorial: Creating A Step-By-Step End-To-End Database Server-Side Blazor Application (Note: you can download the completed application from the Downloads page on this site).

Note: For added security, in the Program.cs file, it is suggested you set: options.SignIn.RequireConfirmedAccount = true and enable email confirmation (see: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-6.0&tabs=visual-studio#register-confirm-email-and-reset-password).

image

We can run the application and click the Register link.

image

We can create an account.

image

We will then be logged in.

image

If we look in the database, we see that an account has been created with a password.

Set Up An External Authentication Provider

image

We will now set up an external authentication provider by following the directions here: Google external login setup in ASP.NET Core.

image

This requires us to install the Microsoft.AspNetCore.Authentication.Google NuGet package.

image

It also requires us to add code like this to the appsettings.json file:

  "Authentication": {
    "Google": {
      "ClientId": "{{ Google Client Id }}",
      "ClientSecret": "{{ Google Client Secret }}"
    }
  },

image

Finally, it requires us to add the following code to the Program.cs file:

builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
    googleOptions.ClientId = 
    builder.Configuration.GetValue<string>("Authentication:Google:ClientId");
    
    googleOptions.ClientSecret = 
    builder.Configuration.GetValue<string>("Authentication:Google:ClientSecret");
});

image

Now when we run the application and click the Log in link we have the option to log in using an external authentication provider.

image

After logging into the external authentication provider, we are redirected back to the application and presented with the page to create an account.

image

If we use an email address that is already used by another account, we are unable to link this external login to the existing account.

Customize The Process

image

The first step to customize the process is to right-click on the project node and select Add then New Scaffolded Item…

Note: If you don’t see this option, Install the Microsoft.VisualStudio.Web.CodeGeneration.Design NuGet package.

image

Select Identity then Add.

image

Wait for the initializations…

image

Select AccountExternalLogin, the Data context class, and press the Add button.

image

The ExternalLogin pages will be created.

Currently they will provide the exact same functionality as before, however, now we can customize them.

Replace all the code with the following code:

ExternalLogin.cshtml

@page
@model ExternalLoginModel
@{
    ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>
<h2 id="external-login-title">Associate your @Model.ProviderDisplayName account.</h2>
<hr />

@if (Convert.ToString(ViewData["AssociateExistingAccount"]) == "false")
{
    <p id="external-login-description" class="text-info">
        You've successfully authenticated with <strong>@Model.ProviderDisplayName</strong>.
        Click the Register button to finish logging in.
    </p>

    <div class="row">
        <div class="col-md-4">
            <form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl"
              method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="form-floating">
                    <input asp-for="Input.Email" class="form-control" readonly="true"
                       autocomplete="email" />
                    <label asp-for="Input.Email" class="form-label"></label>
                    <span asp-validation-for="Input.Email" class="text-danger"></span>
                </div>
                <button type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
            </form>
        </div>
    </div>
}

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

ExternalLogin.cshtml.cs

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable

using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;

namespace EndToEnd.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class ExternalLoginModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IUserStore<IdentityUser> _userStore;
        private readonly IUserEmailStore<IdentityUser> _emailStore;
        private readonly IEmailSender _emailSender;
        private readonly ILogger<ExternalLoginModel> _logger;

        public ExternalLoginModel(
            SignInManager<IdentityUser> signInManager,
            UserManager<IdentityUser> userManager,
            IUserStore<IdentityUser> userStore,
            ILogger<ExternalLoginModel> logger,
            IEmailSender emailSender)
        {
            _signInManager = signInManager;
            _userManager = userManager;
            _userStore = userStore;
            _emailStore = GetEmailStore();
            _logger = logger;
            _emailSender = emailSender;
        }

        [BindProperty]
        public InputModel Input { get; set; }
        public string ProviderDisplayName { get; set; }
        public string ReturnUrl { get; set; }

        [TempData]
        public string ErrorMessage { get; set; }

        [ViewData]
        public string AssociateExistingAccount { get; set; } = "false";

        public class InputModel
        {
            [Required]
            [EmailAddress]
            public string Email { get; set; }

            [DataType(DataType.Password)]
            public string Password { get; set; }
        }

        public IActionResult OnGet() => RedirectToPage("./Login");

        public IActionResult OnPost(string provider, string returnUrl = null)
        {
            // Request a redirect to the external login provider.
            var redirectUrl =
                Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });

            var properties =
                _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);

            return new ChallengeResult(provider, properties);
        }

        public async Task<IActionResult> OnGetCallbackAsync(
            string returnUrl = null, string remoteError = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            if (remoteError != null)
            {
                ErrorMessage = $"Error from external provider: {remoteError}";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }
            var info = await _signInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                ErrorMessage = "Error loading external login information.";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }

            // 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)
            {
                return LocalRedirect(returnUrl);
            }
            if (result.IsLockedOut)
            {
                return RedirectToPage("./Lockout");
            }
            else
            {
                // If the user does not have an account, then ask the user to create an account.
                AssociateExistingAccount = "false";
                ReturnUrl = returnUrl;
                ProviderDisplayName = info.ProviderDisplayName;
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
                {
                    Input = new InputModel
                    {
                        Email = info.Principal.FindFirstValue(ClaimTypes.Email)
                    };
                }
                return Page();
            }
        }

        private IdentityUser CreateUser()
        {
            try
            {
                return Activator.CreateInstance<IdentityUser>();
            }
            catch
            {
                throw new InvalidOperationException($"Can't create an instance of " +
                    $"'{nameof(IdentityUser)}'. " +
                    $"Ensure that '{nameof(IdentityUser)}' is not an abstract class " +
                    $"and has a parameterless constructor, or alternatively " +
                    $"override the external login page in " +
                    $"/Areas/Identity/Pages/Account/ExternalLogin.cshtml");
            }
        }

        private IUserEmailStore<IdentityUser> GetEmailStore()
        {
            if (!_userManager.SupportsUserEmail)
            {
                throw new NotSupportedException(
                    "The default UI requires a user store with email support.");
            }
            return (IUserEmailStore<IdentityUser>)_userStore;
        }
    }
}

image

When we run the project and click the external login provider button…

image

…we are authenticated, but not logged in, and our account is not created yet.

Also, clicking the Register button wont do anything at this point.

To handle the Register button, add the following method to ExternalLogin.cshtml.cs:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        ErrorMessage = "Error loading external login information during confirmation.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = CreateUser();

        await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
        await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);

        var result = await _userManager.CreateAsync(user);

        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);
            if (result.Succeeded)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code },
                    protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by " +
                    $"<a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>" +
                    $"clicking here</a>.");

                // If account confirmation is required, we need to show the link if
                // we don't have a real email sender
                if (_userManager.Options.SignIn.RequireConfirmedAccount)
                {
                    return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
                }

                await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }
        }
        else
        {
            // There is an existing account 
            // Check if that account has a password 
            var ExistingUserToCheck = await _userManager.FindByEmailAsync(user.Email);

            if (ExistingUserToCheck != null)
            {
                if (ExistingUserToCheck.PasswordHash == null)
                {
                    StringBuilder PasswordNotSetError = new StringBuilder();
                    PasswordNotSetError.Append("There is an existing account with that email address. ");
                    PasswordNotSetError.Append("However, that account has no password set. ");
                    PasswordNotSetError.Append("Please log in to that account, with the ");
                    PasswordNotSetError.Append("existing external login method, and set a password. ");
                    PasswordNotSetError.Append("Then you can associate it with additional external ");
                    PasswordNotSetError.Append("login methods.");

                    AssociateExistingAccount = "blocked";
                    ModelState.AddModelError(string.Empty, PasswordNotSetError.ToString());
                    return Page();
                }
            }

            // We can associate this login to the existing account
            AssociateExistingAccount = "true";
        }

        // Display any errors that occurred
        // Usually says email is already used
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    ProviderDisplayName = info.ProviderDisplayName;
    ReturnUrl = returnUrl;
    return Page();
}

Note: When a user logs in, using an external login, if they click Register button or the Log in button, they always end up hitting: public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)

Add the following code to ExternalLogin.cshtml:

@if (Convert.ToString(ViewData["AssociateExistingAccount"]) == "true")
{
    <div class="row">
        <div class="col-md-4">            
            <form asp-page-handler="AssociateLogin" asp-route-returnUrl="@Model.ReturnUrl" 
            method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <p>To associate this external login with the existing account, 
                    please enter the password for that account.</p>
                <div class="form-floating">
                    <input asp-for="Input.Password" class="form-control" autocomplete="off" />
                    <label asp-for="Input.Password" class="form-label"></label>
                    <span asp-validation-for="Input.Password" class="text-danger"></span>
                </div>
                <button type="submit" class="w-100 btn btn-lg btn-primary">Associate Account
                    
                </button>
            </form>
        </div>
    </div>
}

image

Now, when we run the project and click the Register button, we are presented with an option to enter the password of the existing account to associate the login.

However, at this point, clicking the Associate Account button won’t do anything.

Now, add the following code to ExternalLogin.cshtml (to handle the situation where we cannot associate an account because it doesn’t have a password):

@if (Convert.ToString(ViewData["AssociateExistingAccount"]) == "blocked")
{
    <div class="row">
        <div class="col-md-4">
            <form asp-page-handler="#" asp-route-returnUrl="@Model.ReturnUrl"
              method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            </form>
        </div>
    </div>
}

Finally, add the following method to ExternalLogin.cshtml.cs to handle the Associate Account button:

public async Task<IActionResult> OnPostAssociateLoginAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    // Set AssociateExistingAccount so we return to this method on postback
    AssociateExistingAccount = "true";

    // Get the information about the user from the external login provider
    var ExternalLoginUser = await _signInManager.GetExternalLoginInfoAsync();

    if (ExternalLoginUser == null)
    {
        ErrorMessage = "Error loading external login information during confirmation.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (Input.Password != null)
    {
        try
        {
            // Get email of the ExternalLoginUser
            string ExternalLoginUserEmail = "";

            if (ExternalLoginUser.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
            {
                ExternalLoginUserEmail =
                    ExternalLoginUser.Principal.FindFirstValue(ClaimTypes.Email);
            }

            // Check password against user in database
            var user = await _userManager.FindByEmailAsync(ExternalLoginUserEmail);

            if (user != null)
            {
                var CheckPasswordResult =
                    await _userManager.CheckPasswordAsync(user, Input.Password);

                if (CheckPasswordResult)
                {
                    // user found and password is correct
                    // add external login to user and sign in
                    var AddLoginResult =
                        await _userManager.AddLoginAsync(user, ExternalLoginUser);

                    if (AddLoginResult.Succeeded)
                    {
                        await _signInManager.SignInAsync(user, isPersistent: false);
                        return LocalRedirect(returnUrl);
                    }
                    else
                    {
                        foreach (var error in AddLoginResult.Errors)
                        {
                            ModelState.AddModelError(string.Empty, error.Description);
                        }
                    }
                }
                else // password is incorrect
                {
                    ModelState.AddModelError(string.Empty, "Password is incorrect");
                }
            }
        }
        catch (Exception ex)
        {
            ModelState.AddModelError(string.Empty, ex.Message);
            return Page();
        }
    }
    else
    {
        ModelState.AddModelError(string.Empty, "Password is required");
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

image

Now the page will allow us to associate the login to the existing account.

Links

  • Facebook and Google authentication in ASP.NET Core | Microsoft Docs
  • ASP.NET Core Identity 2022: 6. Facebook & Google Auth (https://github.com/teddysmithdev/IdentityAppCourse2022)
  • External Identity Provider with ASP.NET Core Identity — Code Maze (code-maze.com)
  • Enabling authentication using Facebook, Google and other external providers — ASP.NET documentation (jakeydocs.readthedocs.io)
  • ASP.NET Core Google Authentication | by Pritomsarkar | C# Programming | Medium
  • Microsoft’s Google Directions: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-6.0
  • Google’s Google Directions: https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid
  • How to get Google App Verified for Production use: https://support.google.com/cloud/answer/9110914

Download

The project is available on the Downloads page on this site.

You must have Visual Studio 2022 (or higher) installed to run the code.

Я пытаюсь заставить IdentityServer4 работать с ASP.NET Core Identity с помощью собственного UserStore для SSO. Хотя руководства кажутся довольно простыми, и сам процесс аутентификации работает в приложении (другое приложение ASP.NET Core MVC), я получаю следующую ошибку:

Error loading external login information

Моя настройка такова:

Для приложения ASP.NET MVC (клиент):

services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan =TimeSpan.FromMinutes(30);
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";

options.Authority = "https://localhost:5001/";

options.ClientId = "clientId";
options.ClientSecret = "secret";
options.SaveTokens = true;
options.ResponseType = "code id_token";
options.Scope.Add(IdentityServerConstants.StandardScopes.Profile);
options.Scope.Add(IdentityServerConstants.StandardScopes.Email);
options.Scope.Add(IdentityServerConstants.StandardScopes.OfflineAccess);

options.GetClaimsFromUserInfoEndpoint = true;
});

Для приложения IdentityServer4:

services.AddScoped<UserManager<User>, MyUserManager>();
services.AddIdentity<User, UserGroup>()
.AddRoleStore<MyRoleStore>()
.AddUserStore<MyUserStore>()
.AddDefaultTokenProviders();

services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = JwtClaimTypes.Subject;
options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.Name;
options.ClaimsIdentity.RoleClaimType = JwtClaimTypes.Role;
});

services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(OpenIDConfig.GetApiResources())
.AddInMemoryIdentityResources(OpenIDConfig.GetIdentityResources())
.AddInMemoryClients(OpenIDConfig.GetClients())
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator<User>>()
.AddProfileService<ProfileService<User>>();

Основная проблема заключается в том, что я не знаю, с чего начать поиск, почему возникает проблема с этим после успешного потока аутентификации.

I have problem in identity framework using external login like FaceBook but I’m stuck
with this error «Error loading external login information«.

Why has this happened?

enter image description here

Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddControllersWithViews();
            services.AddRazorPages();
            services.AddAuthentication();

            services.AddAuthentication()

                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
                
                .AddFacebook(facebookOptions =>
            {
                facebookOptions.AppId = "";
                facebookOptions.AppSecret ="";
                facebookOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                // facebookOptions.CallbackPath = "/signin-facebook";
              //  facebookOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
            });

ExternalLogInModel.cs

public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            if (remoteError != null)
            {
                ErrorMessage = $"Error from external provider: {remoteError}";
                return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
            }
            var info = await _signInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                ErrorMessage = "Error loading external login information.";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }

            // 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)
            {
                _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }

Понравилась статья? Поделить с друзьями:
  • Error loading editable poly
  • Error loading driver обновление bios
  • Error loading dll game dll
  • Error loading dll cryrenderd3d11 dll error code 126
  • Error loading dll cryrenderd3d10 dll error code 126 archeage