In our ASP.NET Core project users can signup and sign in with their Google account and/or with local account.
We are using custom model MyUser : IdentityUser<Guid> and MyRole : IdentityRole<Guid>.
Everything works well except one weird thing: if user logged in using Google account after some time ASP.NET won't be able to validate his token anymore. Token is the same, Data Protection key is the same. ASP.NET fails to convert ID to Guid.
As you can see on the screenshot below UserId is not a Guid for the some reason. As a result ASP.NET fails to see if such user exists.
This keeps happening after a while. So, I'm able to log in and use the application, but then suddenly it stops working at some point.
System.FormatException: Unrecognized Guid format.
at System.Guid.GuidResult.SetFailure(ParseFailure failureKind)
at System.Guid.TryParseGuid(ReadOnlySpan`1 guidString, GuidResult& result)
at System.Guid..ctor(String g)
at System.ComponentModel.GuidConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at Microsoft.AspNetCore.Identity.UserStoreBase`5.ConvertIdFromString(String id)
at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.FindByIdAsync(String userId, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Identity.UserManager`1.GetUserAsync(ClaimsPrincipal principal)
at Microsoft.AspNetCore.Identity.SignInManager`1.ValidateSecurityStampAsync(ClaimsPrincipal principal)
at Microsoft.AspNetCore.Identity.SecurityStampValidator`1.ValidateAsync(CookieValidatePrincipalContext context)
at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleAuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Builder.Extensions.UsePathBaseMiddleware.InvokeCore(HttpContext context, PathString matchedPath, PathString remainingPath)
services.AddIdentityCore<MyUser>(options => { options.SignIn.RequireConfirmedAccount = false; })
.AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders()
.AddApiEndpoints();
...
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddGoogle(GoogleDefaults.AuthenticationScheme, o =>
{
var conf = configuration.GetSection("Authentication:Google");
o.ClientId = conf["ClientId"];
o.ClientSecret = conf["ClientSecret"];
})
.AddIdentityCookies(c =>
{
c.ApplicationCookie!.Configure(cfg =>
{
cfg.Events.OnRedirectToLogin = ctx =>
{
ctx.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
});
I looked at source code of the AddIdentity method and noticed it configures UserClaimsPrincipalFactory while AddIdentityCore doesn't, so I tried to use code from the AddIdentity, but it didn't make any difference.
services.AddOptions().AddLogging();
services.TryAddScoped<IUserValidator<MyUser>, UserValidator<MyUser>>();
services.TryAddScoped<IPasswordValidator<MyUser>, PasswordValidator<MyUser>>();
services.TryAddScoped<IPasswordHasher<MyUser>, PasswordHasher<MyUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<MyApplicationRole>, RoleValidator<MyApplicationRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<MyUser>>();
services.TryAddEnumerable(ServiceDescriptor
.Singleton<IPostConfigureOptions<SecurityStampValidatorOptions>, MyOptions>());
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<MyUser>>();
services
.TryAddScoped<IUserClaimsPrincipalFactory<MyUser>,
UserClaimsPrincipalFactory<MyUser, MyApplicationRole>>();
# services.TryAddScoped<IUserConfirmation<MyUser>, DefaultUserConfirmation<MyUser>>();
services.TryAddScoped<UserManager<MyUser>>();
services.TryAddScoped<SignInManager<MyUser>>();
services.TryAddScoped<RoleManager<MyApplicationRole>>();
new IdentityBuilder(typeof(MyUser), typeof(MyRole), services)
