Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The instance of entity type 'IdentityUser' cannot be tracked because another instance with the key value #19696

Open
abdullahshaqaliah opened this issue May 2, 2024 · 4 comments

Comments

@abdullahshaqaliah
Copy link

abdullahshaqaliah commented May 2, 2024

Hi
I using Abp framework version 8.0.2
I get this error when try login

[21:16:02 ERR] An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The instance of entity type 'IdentityUser' cannot be tracked because another instance with the key value '{Id: 3a120865-c835-6841-3b9c-583f974de8e0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

We using the Identity 4 server to log in the error happens when calling this code below

  var result = await SignInManager.PasswordSignInAsync(
      LoginInput.UserNameOrEmailAddress,
      LoginInput.Password,
      LoginInput.RememberMe,
      true
  );

This error happened with some users not for all
Can help us to resolve this issue

@abdullahshaqaliah
Copy link
Author

abdullahshaqaliah commented May 2, 2024

The log for error

Executing endpoint '/Account/Login'
[21:43:43 INF] Route matched with {page = "/Account/Login", action = "", controller = "", area = ""}. Executing page /Account/Login
[21:43:43 INF] Skipping the execution of current filter as its not the most effective filter implementing the policy Microsoft.AspNetCore.Mvc.ViewFeatures.IAntiforgeryPolicy
[21:43:45 INF] Executing handler method Volo.Abp.Account.Web.Pages.Account.LoginModel.OnPostAsync - ModelState is Valid
[21:43:45 WRN] Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
[21:43:48 INF] Executed DbCommand (448ms) [Parameters=[@__ef_filter__p_0='False', @__ef_filter__p_1='False', @__NormalizeName_0='HANA.ABD-ELJAWAD@TENTIMEJO.COM', @__NormalizeEmail_1='HANA.ABD-ELJAWAD@TENTIMEJO.COM', @__NormalizePhoneNumber_2='HANA.ABD-ELJAWAD@TENTIMEJO.COM'], CommandType='Text', CommandTimeout='30']
SELECT u."Id", u."AccessFailedCount", u."ConcurrencyStamp", u."CreationTime", u."CreatorId", u."DeleterId", u."DeletionTime", u."Email", u."EmailConfirmed", u."EntityVersion", u."ExtraProperties", u."IsActive", u."IsDeleted", u."IsExternal", u."LastModificationTime", u."LastModifierId", u."LastPasswordChangeTime", u."LockoutEnabled", u."LockoutEnd", u."Name", u."NormalizedEmail", u."NormalizedUserName", u."ParentalLockCode", u."PasswordHash", u."PhoneNumber", u."PhoneNumberConfirmed", u."RestrictMainProfileAccess", u."RestrictManagingProfiles", u."SecurityStamp", u."ShouldChangePasswordOnNextLogin", u."Surname", u."TenantId", u."TwoFactorEnabled", u."UserName"
FROM "Users" AS u
WHERE (@__ef_filter__p_0 OR NOT (u."IsDeleted")) AND (@__ef_filter__p_1 OR u."TenantId" IS NULL) AND (u."NormalizedUserName" = @__NormalizeName_0 OR u."NormalizedEmail" = @__NormalizeEmail_1 OR u."PhoneNumber" = @__NormalizePhoneNumber_2)
ORDER BY u."Id"
LIMIT 1
[21:43:48 INF] Executed DbCommand (398ms) [Parameters=[@__ef_filter__p_0='False', @__ef_filter__p_1='False', @__NormalizeName_0='HANA.ABD-ELJAWAD@TENTIMEJO.COM', @__NormalizeEmail_1='HANA.ABD-ELJAWAD@TENTIMEJO.COM', @__NormalizePhoneNumber_2='HANA.ABD-ELJAWAD@TENTIMEJO.COM'], CommandType='Text', CommandTimeout='30']
SELECT u."Id", u."AccessFailedCount", u."ConcurrencyStamp", u."CreationTime", u."CreatorId", u."DeleterId", u."DeletionTime", u."Email", u."EmailConfirmed", u."EntityVersion", u."ExtraProperties", u."IsActive", u."IsDeleted", u."IsExternal", u."LastModificationTime", u."LastModifierId", u."LastPasswordChangeTime", u."LockoutEnabled", u."LockoutEnd", u."Name", u."NormalizedEmail", u."NormalizedUserName", u."ParentalLockCode", u."PasswordHash", u."PhoneNumber", u."PhoneNumberConfirmed", u."RestrictMainProfileAccess", u."RestrictManagingProfiles", u."SecurityStamp", u."ShouldChangePasswordOnNextLogin", u."Surname", u."TenantId", u."TwoFactorEnabled", u."UserName"
FROM "Users" AS u
WHERE (@__ef_filter__p_0 OR NOT (u."IsDeleted")) AND (@__ef_filter__p_1 OR u."TenantId" IS NULL) AND (u."NormalizedUserName" = @__NormalizeName_0 OR u."NormalizedEmail" = @__NormalizeEmail_1 OR u."PhoneNumber" = @__NormalizePhoneNumber_2)
ORDER BY u."Id"
LIMIT 1
[21:43:49 INF] Executed DbCommand (383ms) [Parameters=[@__ef_filter__p_0='False', @__ef_filter__p_1='False', @__NormalizeName_0='HANA', @__NormalizeEmail_1='HANA', @__NormalizePhoneNumber_2='HANA'], CommandType='Text', CommandTimeout='30']
SELECT u."Id", u."AccessFailedCount", u."ConcurrencyStamp", u."CreationTime", u."CreatorId", u."DeleterId", u."DeletionTime", u."Email", u."EmailConfirmed", u."EntityVersion", u."ExtraProperties", u."IsActive", u."IsDeleted", u."IsExternal", u."LastModificationTime", u."LastModifierId", u."LastPasswordChangeTime", u."LockoutEnabled", u."LockoutEnd", u."Name", u."NormalizedEmail", u."NormalizedUserName", u."ParentalLockCode", u."PasswordHash", u."PhoneNumber", u."PhoneNumberConfirmed", u."RestrictMainProfileAccess", u."RestrictManagingProfiles", u."SecurityStamp", u."ShouldChangePasswordOnNextLogin", u."Surname", u."TenantId", u."TwoFactorEnabled", u."UserName"
FROM "Users" AS u
WHERE (@__ef_filter__p_0 OR NOT (u."IsDeleted")) AND (@__ef_filter__p_1 OR u."TenantId" IS NULL) AND (u."NormalizedUserName" = @__NormalizeName_0 OR u."NormalizedEmail" = @__NormalizeEmail_1 OR u."PhoneNumber" = @__NormalizePhoneNumber_2)
ORDER BY u."Id"
LIMIT 1
[21:43:49 INF] Executed DbCommand (416ms) [Parameters=[@__ef_filter__p_0='False', @__ef_filter__p_1='False', @__normalizedEmail_0='HANA.ABD-ELJAWAD@TENTIMEJO.COM'], CommandType='Text', CommandTimeout='30']
SELECT u."Id", u."AccessFailedCount", u."ConcurrencyStamp", u."CreationTime", u."CreatorId", u."DeleterId", u."DeletionTime", u."Email", u."EmailConfirmed", u."EntityVersion", u."ExtraProperties", u."IsActive", u."IsDeleted", u."IsExternal", u."LastModificationTime", u."LastModifierId", u."LastPasswordChangeTime", u."LockoutEnabled", u."LockoutEnd", u."Name", u."NormalizedEmail", u."NormalizedUserName", u."ParentalLockCode", u."PasswordHash", u."PhoneNumber", u."PhoneNumberConfirmed", u."RestrictMainProfileAccess", u."RestrictManagingProfiles", u."SecurityStamp", u."ShouldChangePasswordOnNextLogin", u."Surname", u."TenantId", u."TwoFactorEnabled", u."UserName"
FROM "Users" AS u
WHERE (@__ef_filter__p_0 OR NOT (u."IsDeleted")) AND (@__ef_filter__p_1 OR u."TenantId" IS NULL) AND u."NormalizedEmail" = @__normalizedEmail_0
ORDER BY u."Id"
LIMIT 1
[21:43:50 INF] Executed DbCommand (281ms) [Parameters=[@__ef_filter__p_0='False', @__ef_filter__p_1='False', @__normalizedEmail_0='HANA'], CommandType='Text', CommandTimeout='30']
SELECT u."Id", u."AccessFailedCount", u."ConcurrencyStamp", u."CreationTime", u."CreatorId", u."DeleterId", u."DeletionTime", u."Email", u."EmailConfirmed", u."EntityVersion", u."ExtraProperties", u."IsActive", u."IsDeleted", u."IsExternal", u."LastModificationTime", u."LastModifierId", u."LastPasswordChangeTime", u."LockoutEnabled", u."LockoutEnd", u."Name", u."NormalizedEmail", u."NormalizedUserName", u."ParentalLockCode", u."PasswordHash", u."PhoneNumber", u."PhoneNumberConfirmed", u."RestrictMainProfileAccess", u."RestrictManagingProfiles", u."SecurityStamp", u."ShouldChangePasswordOnNextLogin", u."Surname", u."TenantId", u."TwoFactorEnabled", u."UserName"
FROM "Users" AS u
WHERE (@__ef_filter__p_0 OR NOT (u."IsDeleted")) AND (@__ef_filter__p_1 OR u."TenantId" IS NULL) AND u."NormalizedEmail" = @__normalizedEmail_0
ORDER BY u."Id"
LIMIT 1
[21:43:53 ERR] ---------- RemoteServiceErrorInfo ----------
{
  "code": null,
  "message": "An internal error occurred during your request!",
  "details": null,
  "data": {},
  "validationErrors": null
}

[21:43:53 ERR] The instance of entity type 'IdentityUser' cannot be tracked because another instance with the key value '{Id: 3a120865-c835-6841-3b9c-583f974de8e0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
System.InvalidOperationException: The instance of entity type 'IdentityUser' cannot be tracked because another instance with the key value '{Id: 3a120865-c835-6841-3b9c-583f974de8e0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Attach(TEntity entity)
   at Volo.Abp.Domain.Repositories.EntityFrameworkCore.EfCoreRepository`2.UpdateAsync(TEntity entity, Boolean autoSave, CancellationToken cancellationToken)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Volo.Abp.Identity.IdentityUserStore.UpdateAsync(IdentityUser user, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Identity.UserManager`1.UpdateUserAsync(TUser user)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Microsoft.AspNetCore.Identity.UserManager`1.ResetAccessFailedCountAsync(TUser user)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Microsoft.AspNetCore.Identity.SignInManager`1.ResetLockout(TUser user)
   at Microsoft.AspNetCore.Identity.SignInManager`1.ResetLockoutWithResult(TUser user)
   at Microsoft.AspNetCore.Identity.SignInManager`1.CheckPasswordSignInAsync(TUser user, String password, Boolean lockoutOnFailure)
   at Microsoft.AspNetCore.Identity.SignInManager`1.PasswordSignInAsync(TUser user, String password, Boolean isPersistent, Boolean lockoutOnFailure)
   at Microsoft.AspNetCore.Identity.SignInManager`1.PasswordSignInAsync(String userName, String password, Boolean isPersistent, Boolean lockoutOnFailure)
   at Volo.Abp.Identity.AspNetCore.AbpSignInManager.PasswordSignInAsync(String userName, String password, Boolean isPersistent, Boolean lockoutOnFailure)
   at TenTime.IdentityServer.Pages.Account.IdentityServerLoginModel.OnPostAsync(String action) in F:\TenTimeWork\Applications\authentication_server\src\TenTime.IdentityServer\Pages\Account\IdentityServerLoginModel.cs:line 157
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

@maliming
Copy link
Member

maliming commented May 3, 2024

hi

Please share the code of IdentityServerLoginModel class.

@abdullahshaqaliah
Copy link
Author

`using IdentityServer4.Events;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using TenTime.IdentityServer.AspNetIdentity;
using TenTime.IdentityServer.Mtv;
using TenTime.IdentityService.Identity;
using Volo.Abp;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using IdentityUser = Volo.Abp.Identity.IdentityUser;

namespace TenTime.IdentityServer.Pages.Account;
[ExposeServices(typeof(LoginModel))]

public class IdentityServerLoginModel : IdentityServerSupportedLoginModel
{
private readonly MtvOptions _mtvOptions;

private readonly TenTimeUserManager _userManager;



private string _externalProvider = "external_provider";
private readonly ILinkAccountRepository _linkAccountRepository;

public IdentityServerLoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IOptions<IdentityOptions> identityOptions, IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, IIdentityServerInteractionService interaction, IClientStore clientStore, IEventService identityServerEvents,
    IOptions<MtvOptions> mtvOptions = null,
    TenTimeUserManager userManager = null, ILinkAccountRepository linkAccountRepository = null) : base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache, interaction, clientStore, identityServerEvents)
{
    _mtvOptions = mtvOptions.Value;
    _userManager = userManager;
    _linkAccountRepository = linkAccountRepository;
}

public override async Task<IActionResult> OnGetAsync()
{

    LoginInput = new LoginInputModel();

    var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl);

    if (context != null)
    {
        // TODO: Find a proper cancel way.
        // ShowCancelButton = true;

        LoginInput.UserNameOrEmailAddress = context.LoginHint;

        //TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
        var tenant = context.Parameters[TenantResolverConsts.DefaultTenantKey];
        if (!string.IsNullOrEmpty(tenant))
        {
            CurrentTenant.Change(Guid.Parse(tenant));
            Response.Cookies.Append(TenantResolverConsts.DefaultTenantKey, tenant);
        }


        var externalProviderLogin = context.Parameters.Get(_externalProvider);
        if (!externalProviderLogin.IsNullOrWhiteSpace())
        {
            if (await SchemeProvider.GetSchemeAsync(externalProviderLogin).ConfigureAwait(false) != null)
            {
                return await base.OnPostExternalLogin(externalProviderLogin);
            }
        }

    }




    if (context?.IdP != null)
    {

        LoginInput.UserNameOrEmailAddress = context.LoginHint;
        ExternalProviders = new[] { new ExternalProviderModel { AuthenticationScheme = context.IdP } };
        return Page();
    }


    var providers = await GetExternalProviders();
    ExternalProviders = providers.ToList();

    EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);


    if (context?.Client?.ClientId != null)
    {
        var client = await ClientStore.FindEnabledClientByIdAsync(context?.Client?.ClientId);
        if (client != null)
        {
            EnableLocalLogin = client.EnableLocalLogin;

            if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
            {
                providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
            }
        }
    }

    if (IsExternalLoginOnly)
    {
        return await base.OnPostExternalLogin(providers.First().AuthenticationScheme);
    }

    return Page();
}
public override async Task<IActionResult> OnPostAsync(string action)
{
    var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl);
    if (action == "Cancel")
    {
        if (context == null)
        {
            return Redirect("~/");
        }

        await Interaction.GrantConsentAsync(context, new ConsentResponse()
        {
            Error = AuthorizationError.AccessDenied
        });

        return Redirect(ReturnUrl);
    }

    await CheckLocalLoginAsync();

    ValidateModel();

    await IdentityOptions.SetAsync();

    ExternalProviders = await GetExternalProviders();

    EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);

    await ReplaceEmailToUsernameOfInputIfNeeds();

    var result = await SignInManager.PasswordSignInAsync(
        LoginInput.UserNameOrEmailAddress,
        LoginInput.Password,
        LoginInput.RememberMe,
        true
    );

    await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
    {
        Identity = IdentitySecurityLogIdentityConsts.Identity,
        Action = result.ToIdentitySecurityLogAction(),
        UserName = LoginInput.UserNameOrEmailAddress,
        ClientId = context?.Client?.ClientId
    });

    if (result.RequiresTwoFactor)
    {
        return await TwoFactorLoginResultAsync();
    }

    if (result.IsLockedOut)
    {
        Alerts.Warning(L["UserLockedOutMessage"]);
        return Page();
    }

    if (result.IsNotAllowed)
    {
        Alerts.Warning(L["LoginIsNotAllowed"]);
        return Page();
    }

    if (!result.Succeeded)
    {
        Alerts.Danger(L["InvalidUserNameOrPassword"]);
        return Page();
    }

    //TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
    var user = await _userManager.FindByAccountAsync(LoginInput.UserNameOrEmailAddress).ConfigureAwait(false);




    Debug.Assert(user != null, nameof(user) + " != null");
    await IdentityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented
                                                                                                                        // Clear the dynamic claims cache.
    await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
    return  await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}

protected  async Task<IdentityUser> CreateExternalUserAsync(ExternalLoginInfo info)
{
    await IdentityOptions.SetAsync();

    var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email) ?? info.Principal.Identities.First().FindFirst(ClaimTypes.Email)?.Value;


    //var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email);

    var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id);

    CheckIdentityErrors(await UserManager.CreateAsync(user));
    CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress));
    CheckIdentityErrors(await UserManager.AddLoginAsync(user, info));
    CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user));

    user.Name = info.Principal.FindFirstValue(AbpClaimTypes.Name);
    user.Surname = info.Principal.FindFirstValue(AbpClaimTypes.SurName);
    user.SetEmailConfirmed(true);
    user.IsExternal = true;
    var phoneNumber = info.Principal.FindFirstValue(AbpClaimTypes.PhoneNumber);
    if (!phoneNumber.IsNullOrWhiteSpace())
    {
        var phoneNumberConfirmed = string.Equals(info.Principal.FindFirstValue(AbpClaimTypes.PhoneNumberVerified), "true", StringComparison.InvariantCultureIgnoreCase);
        user.SetPhoneNumber(phoneNumber, phoneNumberConfirmed);
    }

    await UserManager.UpdateAsync(user);

    return user;
}
public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
{
    //TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample)
    /* Also did not implement these:
     * - Logout(string logoutId)
     */

    if (remoteError != null)
    {
        Logger.LogWarning($"External login callback error: {remoteError}");
        return RedirectToPage("./Login");
    }

    await IdentityOptions.SetAsync();

    var loginInfo = await SignInManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
    {
        Logger.LogWarning("External login info is not available");
        return RedirectToPage("./Login");
    }

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

    if (!result.Succeeded)
    {
        await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
        {
            Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
            Action = "Login" + result
        });
    }

    if (result.IsLockedOut)
    {
        Logger.LogWarning($"External login callback error: user is locked out!");
        throw new UserFriendlyException("Cannot proceed because user is locked out!");
    }

    if (result.IsNotAllowed)
    {
        Logger.LogWarning($"External login callback error: user is not allowed!");
        throw new UserFriendlyException("Cannot proceed because user is not allowed!");
    }

    if (result.Succeeded)
    {
        return await RedirectSafelyAsync(returnUrl, returnUrlHash);
    }

    //TODO: Handle other cases for result!

    var email = loginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) ?? loginInfo.Principal.Identities.First().FindFirst(ClaimTypes.Email)?.Value;
    if (email.IsNullOrWhiteSpace())
    {
        Logger.LogWarning("External login info is not available");
        return RedirectToPage("./Login");
        //return RedirectToPage("./Register", new
        //{
        //    IsExternalLogin = true,
        //    ExternalLoginAuthSchema = loginInfo.LoginProvider,
        //    ReturnUrl = returnUrl
        //});
        //var identity = loginInfo.Principal.Identities.First();
        //var emailClaim = identity.FindFirst(ClaimTypes.Email);

        //if (emailClaim == null)
        //{
        //    Logger.LogWarning("External login info is not available");
        //    return RedirectToPage("./Login");
        //}
        //email = emailClaim.Value;
    }
    IdentityUser user;
    var linkAccount = await _linkAccountRepository.GetAsync(loginInfo.LoginProvider, email).ConfigureAwait(false);
    if (linkAccount == null)
    {
         user = await UserManager.FindByEmailAsync(email);
        if (user == null)
        {
            user = await CreateExternalUserAsync(loginInfo);
        }
        else
        {
            if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null)
            {
                CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo));
            }
        }
    }
    else
    {
        user = await _userManager.FindByIdAsync(linkAccount.UserId.ToString()).ConfigureAwait(false);

    }

    await SignInManager.SignInAsync(user, false);

    await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
    {
        Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
        Action = result.ToIdentitySecurityLogAction(),
        UserName = user.Name
    });
    return await RedirectSafelyAsync(returnUrl, returnUrlHash);
}

public override async Task<IActionResult> OnPostExternalLogin(string provider)
{
    var redirectUrl = Url.Page("./LoginMtv", values: new { ReturnUrl, ReturnUrlHash });
    
    if (provider== _mtvOptions.LoginProvider)
    {
        return await Task.FromResult(Redirect(redirectUrl));
    }

    return await base.OnPostExternalLogin(provider).ConfigureAwait(false);
}

}

`

I resolved the issue by overriding two method on the class SignInManager to stop this issue
the problem with checking whether the user has a locked account or not
`public class TenTimeSignInManager : AbpSignInManager
{
public TenTimeSignInManager(IdentityUserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory, IOptions optionsAccessor, ILogger<SignInManager<Volo.Abp.Identity.IdentityUser>> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation, IOptions options, ISettingProvider settingProvider) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation, options, settingProvider)
{
}

protected override  Task ResetLockout(Volo.Abp.Identity.IdentityUser user)
{
    return Task.CompletedTask;
}
public override async Task<SignInResult> CheckPasswordSignInAsync(Volo.Abp.Identity.IdentityUser user, string password, bool lockoutOnFailure)
{
    ArgumentNullException.ThrowIfNull(user);

    var error = await PreSignInCheck(user);
    if (error != null)
    {
        return error;
    }

    if (await UserManager.CheckPasswordAsync(user, password))
    {
        var alwaysLockout = AppContext.TryGetSwitch("Microsoft.AspNetCore.Identity.CheckPasswordSignInAlwaysResetLockoutOnSuccess", out var enabled) && enabled;
        // Only reset the lockout when not in quirks mode if either TFA is not enabled or the client is remembered for TFA.
        if (alwaysLockout || !await IsTwoFactorEnabledAsync(user) || await IsTwoFactorClientRememberedAsync(user))
        {
            var resetLockoutResult = await ResetLockoutWithResult(user);
            if (!resetLockoutResult.Succeeded)
            {
                // ResetLockout got an unsuccessful result that could be caused by concurrency failures indicating an
                // attacker could be trying to bypass the MaxFailedAccessAttempts limit. Return the same failure we do
                // when failing to increment the lockout to avoid giving an attacker extra guesses at the password.
                return SignInResult.Failed;
            }
        }

        return SignInResult.Success;
    }
    //Logger.LogDebug(EventIds.InvalidPassword, "User failed to provide the correct password.");

    if (UserManager.SupportsUserLockout && lockoutOnFailure)
    {
        // If lockout is requested, increment access failed count which might lock out the user
        var incrementLockoutResult = /*await UserManager.AccessFailedAsync(user) ??*/ IdentityResult.Success;
        if (!incrementLockoutResult.Succeeded)
        {
            // Return the same failure we do when resetting the lockout fails after a correct password.
            return SignInResult.Failed;
        }

        if (await UserManager.IsLockedOutAsync(user))
        {
            return await LockedOut(user);
        }
    }
    return SignInResult.Failed;
}

private async Task<IdentityResult> ResetLockoutWithResult(Volo.Abp.Identity.IdentityUser user)
{
    // Avoid relying on throwing an exception if we're not in a derived class.
    if (GetType() == typeof(SignInManager<Volo.Abp.Identity.IdentityUser>))
    {
        if (!UserManager.SupportsUserLockout)
        {
            return IdentityResult.Success;
        }

        return await UserManager.ResetAccessFailedCountAsync(user) ?? IdentityResult.Success;
    }

    var resetLockoutTask = ResetLockout(user);

    if (resetLockoutTask is Task<IdentityResult> resultTask)
    {
        return await resultTask ?? IdentityResult.Success;
    }

    await resetLockoutTask;
    return IdentityResult.Success;
    //try
    //{
    //    var resetLockoutTask = ResetLockout(user);

    //    if (resetLockoutTask is Task<IdentityResult> resultTask)
    //    {
    //        return await resultTask ?? IdentityResult.Success;
    //    }

    //    await resetLockoutTask;
    //    return IdentityResult.Success;
    //}
    //catch (IdentityResultException ex)
    //{
    //    return ex.IdentityResult;
    //}
}

}
`
The problem with methods
ResetLockout and UserManager.AccessFailedAsync(user)

@maliming
Copy link
Member

maliming commented May 6, 2024

How can I reproduce this in a new template project?
Or can you share a simple project?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants