1

I'm developing a web API using .NET 5. I have an endpoint in that gets the current user

    [Authorize]
    [HttpGet]
    public async Task<ActionResult<UserDto>> GetCurrentUser()
    {
        ApplicationUser user = await userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));

        return CreateUserObject(user);
    }

Now, if I add the dependency like below, this method works fine.

services.AddIdentityCore<ApplicationUser>(opt =>
        {
            //options go here
            opt.User.RequireUniqueEmail = true;
        })

However, if I use AddIdentity() instead of AddIdentityCore() this method fails

        // AddIdentity registers the same services as AddIdentityCore, with a few extras:
        // https://stackoverflow.com/questions/55361533/addidentity-vs-addidentitycore
        services.AddIdentity<ApplicationUser, IdentityRole>(opt =>
        //services.AddIdentityCore<ApplicationUser>(opt =>
        {
            //options go here
            opt.User.RequireUniqueEmail = true;
        })
        .AddEntityFrameworkStores<DataContext>()
        .AddSignInManager<SignInManager<ApplicationUser>>();


        SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(config["TokenKey"]));

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = key,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                };
            });
        services.AddScoped<TokenService>();

"statusCode": 500, "message": "Value cannot be null. (Parameter 'email')", "details": " at Microsoft.AspNetCore.Identity.UserManager1.FindByEmailAsync(String email)\r\n at Spacestation.API.Controllers.AccountController.GetCurrentUser() in F:\\Projects\\Spacestation\\Spacestation.API\\Controllers\\AccountController.cs:line 86\r\n at lambda_method18(Closure , Object )\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask1 actionResultValueTask)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n--- End of stack trace from previous location ---\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\r\n at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)\r\n at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\r\n at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)\r\n at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)\r\n at Spacestation.API.Middleware.ExceptionMiddleware.InvokeAsync(HttpContext context) in F:\Projects\Spacestation\Spacestation.API\Middleware\ExceptionMiddleware.cs:line 28"

Edit #1 enter image description here

Edit #2 - The token services

public string CreateToken(ApplicationUser user)
    {
        List<Claim> claims = new()
        {
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Email, user.Email),
        };

        SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(config["TokenKey"]));
        SigningCredentials creds = new(key, SecurityAlgorithms.HmacSha512Signature);

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.Now.AddDays(7),
            SigningCredentials = creds
        };

        var tokenHandler = new JwtSecurityTokenHandler();

        var token = tokenHandler.CreateToken(tokenDescriptor);

        return tokenHandler.WriteToken(token);

    }
Adam
  • 482
  • 1
  • 8
  • 18
  • Could you pls check if there's `ClaimTypes.Email` in `User`? As we know that `AddIdentity()` contains the implement of `AddIdentityCore()` like [this answer](https://stackoverflow.com/a/55361961) said. And I also found a [similar question here](https://github.com/dotnet/aspnetcore/issues/18348) – Tiny Wang Aug 23 '21 at 07:52
  • And I also don't know how you get the email when using `AddIdentityCore()` . Whether or not you set the email as a custom property? And this is my debug screenshot: https://i.stack.imgur.com/V97XA.png – Tiny Wang Aug 23 '21 at 08:43
  • I referred to this tutorial to generate my test project and this is my configure.https://i.stack.imgur.com/j1Tc0.png – Tiny Wang Aug 23 '21 at 08:45
  • So when using `AddIdentityCore` I have these properties in User. But they are not present in `AddIdentity` See edit #1 – Adam Aug 23 '21 at 09:05
  • Could you debug before for User beign authenticated? – Alberto León Aug 23 '21 at 09:19
  • @TinyWang I've also added my token service, is there perhaps a different way token claims need to be set between `identity` and `identitycore`? – Adam Aug 23 '21 at 10:45
  • @Adam the test I executed is based on asp.net core 3.1 but not 5, and as this high [vote answer](https://stackoverflow.com/a/52135130/14574199) said `User.FindFirstValue(ClaimTypes.Email)` is for net 5 so I created a new project today and I found it uses `services.AddDefaultIdentity` by default. And I search for the difference. – Tiny Wang Aug 24 '21 at 08:43

2 Answers2

0

I'm not sure if this can work for you, but I added few configurations more and an additional AddAuthentication:

        services.AddIdentity<IdentityUser, IdentityRole>(
        options => {
            options.SignIn.RequireConfirmedAccount = true;
            options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
            options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultEmailProvider;
            options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
        })
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
       .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, act => {
           act.LoginPath = "/identity/account/login";
           act.AccessDeniedPath = "/identity/account/accessdenied";
           act.SlidingExpiration = true;
       });
Alberto León
  • 2,879
  • 2
  • 25
  • 24
0

I newly created a net 5 mvc project and Change Authentication to Individual User Accounts and I found by default it uses services.AddDefaultIdentity, so I query the difference between it and services.AddIdentity and the result is like

Calling AddDefaultIdentity is similar to calling the following:

AddIdentity

AddDefaultUI

AddDefaultTokenProviders

And, this works for me :

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDatabaseDeveloperPageExceptionFilter();

            //services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
            //    .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddIdentity<IdentityUser, IdentityRole>(opt =>
            {
                opt.User.RequireUniqueEmail = true;
            }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders().AddDefaultUI();
            services.AddControllersWithViews();
        }

enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • I added the `AddDefaultTokenProviders()` method to my services, however, I still don't have any claims set in my `User`. I also don't have a `AddDefaultUI()` either. I dont understand why in the core version my claims are being set, but not when using `AddIdentity()` – Adam Aug 25 '21 at 20:44
  • I think it's better to report issue to github in the [official document](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.identityservicecollectionextensions.addidentity?view=aspnetcore-5.0). – Tiny Wang Aug 26 '21 at 01:54