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

Null value being passed to the Claim constructor in the UserClaimsPrincipalFactory<TUser> on Identity Endpoint Login .Net 8 #54669

Closed
1 task done
BillyMartin1964 opened this issue Mar 21, 2024 · 6 comments
Assignees
Labels
area-identity Includes: Identity and providers ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved

Comments

@BillyMartin1964
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When trying to login with Identity Endpoint, I get a null reference exception having to do with User Claims, even when using Swagger.

Expected Behavior

I expect the endpoint to log the user in

Steps To Reproduce

No response

Exceptions (if any)

System.ArgumentNullException: Value cannot be null. (Parameter 'value')
at System.ArgumentNullException.Throw(String paramName)
at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
at System.Security.Claims.Claim..ctor(String type, String value)
at Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory1.GenerateClaimsAsync(TUser user) at Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory1.CreateAsync(TUser user)
at Microsoft.AspNetCore.Identity.SignInManager1.CreateUserPrincipalAsync(TUser user) at Microsoft.AspNetCore.Identity.SignInManager1.SignInWithClaimsAsync(TUser user, AuthenticationProperties authenticationProperties, IEnumerable1 additionalClaims) at Microsoft.AspNetCore.Identity.SignInManager1.SignInOrTwoFactorAsync(TUser user, Boolean isPersistent, String loginProvider, Boolean bypassTwoFactor)
at Microsoft.AspNetCore.Identity.SignInManager1.PasswordSignInAsync(TUser user, String password, Boolean isPersistent, Boolean lockoutOnFailure) at Microsoft.AspNetCore.Identity.SignInManager1.PasswordSignInAsync(String userName, String password, Boolean isPersistent, Boolean lockoutOnFailure)
at Microsoft.AspNetCore.Routing.IdentityApiEndpointRouteBuilderExtensions.<>c__1`1.<b__1_1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F69328E0708B4B584C5AACA22FE2C51A1CF192D6622828F613FC57C583CA77B63__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass4_0.<g__RequestHandler|4>d.MoveNext()
--- End of stack trace from previous location ---
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS

Accept: application/json
Host: localhost:7084
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Type: application/json
Cookie: .AspNetCore.Antiforgery.BE_PV6cpjCg=CfDJ8JbHcuPH-DZNo-GwGXZOpygZQENsHXZjq8U-Es8VQl2bbpxJkZ13tAc9WwxIcgDo5zCL59gI7gZqKQYFZDItiG9S3YBj6uwZPAqjcG7x7hKuROCRFATbBKp4EopAtOPj9olJ18mWPJUrHAN69uTGH9Y
Origin: https://localhost:7084
Referer: https://localhost:7084/swagger/index.html
Content-Length: 65
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-site: same-origin
sec-fetch-mode: cors
sec-fetch-dest: empty

.NET Version

8

Anything else?

Using Blazor webapp with Identity Endpoints enabled. Tried it with and without authorization.

using Serilog;
using BlazorApp.Client.Pages;
using BlazorApp.Components;
using BlazorApp.Components.Account;
using BlazorApp.Data;
using BlazorApp.Features.Account;
using Microsoft.AppCenter.Ingestion.Models;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Log = Serilog.Log;
using Microsoft.OpenApi.Models;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Filters;
using System;

Log.Logger = new LoggerConfiguration()
      // .ReadFrom.Configuration(builder.Configuration).CreteLogger();// This line is when using appsettings. 
      .WriteTo.Console()
      .WriteTo.File("zlogs/log.txt", rollingInterval: RollingInterval.Day)
      .CreateLogger();

try
{

    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddControllers();
    builder.Services.AddEndpointsApiExplorer();

    builder.Services.AddDbContextPool<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("BlazorServerConnection"), b => b.MigrationsAssembly("BlazorServer")));

    //builder.Services.AddAuthentication();

    builder.Services.AddAuthentication(options =>
        {
            options.DefaultScheme = IdentityConstants.ApplicationScheme;
            options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
        })
        .AddBearerToken();


    builder.Services.AddIdentityCore<User>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddSignInManager()
        .AddDefaultTokenProviders();

    builder.Services.AddIdentityApiEndpoints<User>(opt =>
    {
        opt.Password.RequiredLength = 8;
        opt.User.RequireUniqueEmail = true;
        opt.Password.RequireNonAlphanumeric = false;
        opt.SignIn.RequireConfirmedEmail = false;
        opt.SignIn.RequireConfirmedPhoneNumber = false;
        opt.SignIn.RequireConfirmedAccount = false;
    })
      .AddEntityFrameworkStores<ApplicationDbContext>();


    // AddSwaggerGen configuration
    builder.Services.AddSwaggerGen(options =>
    {
        options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

        // Add security definition for OAuth2
        options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
        {
            In = ParameterLocation.Header,
            Name = "Authorization",
            Type = SecuritySchemeType.ApiKey
        });

        // Add security requirement for OAuth2
        options.OperationFilter<SecurityRequirementsOperationFilter>();
    });

    // Add services to the container.
    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents()
        .AddInteractiveWebAssemblyComponents();

    builder.Services.AddScoped<IUserRepository, UserRepository>();
    builder.Services.AddScoped<ITokenService, TokenService>();

    builder.Services.AddCascadingAuthenticationState();
    builder.Services.AddScoped<IdentityUserAccessor>();
    builder.Services.AddScoped<IdentityRedirectManager>();
    builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();

 
    //   builder.Services.AddAuthorization();


    builder.Services.AddDatabaseDeveloperPageExceptionFilter();


    // builder.Services.AddSingleton<IEmailSender<MobileUser>, IdentityNoOpEmailSender>();
    
    var app = builder.Build();

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
    });


    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseWebAssemblyDebugging();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error", createScopeForErrors: true);
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

   // app.UseAuthorization();

    app.MapIdentityApi<User>();

    app.UseHttpsRedirection();

    app.UseStaticFiles();
    app.UseAntiforgery();

    app.MapRazorComponents<App>()
        .AddInteractiveServerRenderMode()
        .AddInteractiveWebAssemblyRenderMode()
        .AddAdditionalAssemblies(typeof(BlazorApp.Client._Imports).Assembly);


app.MapControllers();

    app.Run();

}
catch (Exception ex)
{
    Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
    Log.CloseAndFlush();
}

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-identity Includes: Identity and providers label Mar 21, 2024
@BillyMartin1964
Copy link
Author

Also, when I use the identity endpoint to register, only the the Normalized Email, Normalized Username, PasswordHash, SecurityStamp, and ConcurrencyStamp get filled in.

@mkArtakMSFT mkArtakMSFT added the Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue label Mar 28, 2024
@dotnet-policy-service dotnet-policy-service bot added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Mar 28, 2024
@BillyMartin1964
Copy link
Author

No Problem. Here it is:

https://github.com/BillyMartin1964/IdentityEndpointError.git

@BillyMartin1964
Copy link
Author

What more feedback do you need?

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. Status: No Recent Activity labels Apr 8, 2024
@BillyMartin1964
Copy link
Author

Is there any activity on this?

@martincostello martincostello removed the Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue label Apr 23, 2024
@halter73 halter73 added investigate Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. labels Apr 24, 2024
@halter73 halter73 added this to the 9.0-preview6 milestone Apr 24, 2024
@halter73 halter73 self-assigned this Apr 24, 2024
@mkArtakMSFT mkArtakMSFT removed the Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. label May 29, 2024
@mkArtakMSFT mkArtakMSFT modified the milestones: 9.0-preview7, 9.0-rc1 Jul 3, 2024
@MackinnonBuck MackinnonBuck modified the milestones: 9.0-rc1, 9.0-rc2 Aug 12, 2024
@mkArtakMSFT mkArtakMSFT modified the milestones: 9.0-rc2, .NET 10 Planning Sep 16, 2024
@mikekistler mikekistler removed this from the .NET 10 Planning milestone Nov 21, 2024
@halter73
Copy link
Member

Also, when I use the identity endpoint to register, only the the Normalized Email, Normalized Username, PasswordHash, SecurityStamp, and ConcurrencyStamp get filled in.

There's a lot of possible ways to extend the endpoints in MapIdentityApi. The ability to add new required registration parameters and validation is part of that, and we've decided the best way to address that is to scaffold the endpoints added by MapIdentityApi so you can have full control similar to the Identity Razor and Blazor pages. This is tracked by #53343.

In the meantime, it's very easy to copy the implementation of MapIdentityApi from the source code. You can either hit F12 on MapIdentityApi to view source or copy it from here. It doesn't rely on any private or internal types in other files, so it should be easy to take it, replace RegisterRequest with something that includes extra required parameters like FirstName and do any extra validation you want before calling UserManager.CreateAsync.

Another thing to note about your IdentityEndpointError repro is that your User it derives from IdentityUser which is an IdentityUser<string> and then hides the public virtual TKey Id { get; set; } with your own [Key] public int Id { get; set; }. This is what's causing issues with things like UserManager.FindByIdAsync and UserManager.GetUserIdAsync. And in turn this is what's causing the ArgumentNullException in GenerateClaimsAsync when the userId claim is the null string from the underlying IdentityUser<string>.Id property.

var userId = await UserManager.GetUserIdAsync(user).ConfigureAwait(false);
var userName = await UserManager.GetUserNameAsync(user).ConfigureAwait(false);
var id = new ClaimsIdentity("Identity.Application", // REVIEW: Used to match Application scheme
Options.ClaimsIdentity.UserNameClaimType,
Options.ClaimsIdentity.RoleClaimType);
id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));

If you update your User class to public class User : IdentityUser<int> and get rid of the Id property in the derived class, UserManager.GetUserIdAsync will return IdentityUser<int>.Id.ToString() and things should work as expected.

@halter73 halter73 added ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. and removed investigate labels Dec 20, 2024
Copy link
Contributor

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-identity Includes: Identity and providers ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved
Projects
None yet
Development

No branches or pull requests

6 participants