Search code examples
asp.net-coreauthenticationclaims-based-identity

How to get the current authenticated user from the controller endpoint in ASP.NET Core?


I am trying to get the current authenticated user of my web app but all I get is null for both the username and the id when I trigger my endpoint from postman.

This is what I have tried in my controller :

private readonly IHttpContextAccessor _httpContextAccessor;

public AccountController(IHttpContextAccessor httpContextAccessor)
{
    _httpContextAccessor = httpContextAccessor;
}

[HttpGet]
public async Task<ActionResult<User>> GetUser()
{
    string? username = _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;

    var id = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

    var user = await _userManager.Users.FirstOrDefaultAsync(o => o.Id == id);

    return Ok(user);
}

I even tried to create an extension but got no luck either :

public static class ClaimsPrincipalExtension
{
    public static string GetUsername(this ClaimsPrincipal user)
    {
        return user.FindFirst(ClaimTypes.Name)?.Value;
    }

    public static string GetUserId(this ClaimsPrincipal user)
    {
        return user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    }
}

This is my Program.cs (In case I am missing something in the services or middlewares) :

// Add services to the container.
builder.Services.AddRazorPages();

builder.Services.AddIdentityServices(builder.Configuration);
builder.Services.AddApplicationServices(builder.Configuration);

//Handling json:

builder.Services.AddControllers()
    .AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

builder.Services.Configure<IdentityOptions>(options =>
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);


builder.Services.AddHttpContextAccessor();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToAccessDenied =
        options.Events.OnRedirectToLogin = context =>
        {
            if (context.Request.Method != "GET")
            {
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                return Task.FromResult<object>(null);
            }
            context.Response.Redirect(context.RedirectUri);
            return Task.FromResult<object>(null);
        };
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // 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.UseCors(builder =>
{
    builder.WithOrigins("https://localhost:4200")
        .AllowCredentials()
        .AllowAnyMethod()
        .AllowAnyHeader();
});

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

And here are my Identity and Application extensions :

public static class ApplicationExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration config)
    {
        services.AddDbContext<DataContext>(opt =>
        {
            object value = opt.UseSqlServer(config.GetConnectionString("DefaultConnection"));
            opt.EnableSensitiveDataLogging();
        });

        services.AddCors();

        services.AddScoped<ITokenService, TokenService>();

        services.AddHttpContextAccessor();

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        services.AddScoped<LogUserActivity>();

        services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

        return services;
    }
}

public static class IdentityExtensions
{
    public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
    {
        services.AddIdentity<User, IdentityRole>(
            opt =>
            {
                opt.Password.RequireDigit = true;
                opt.Password.RequireLowercase = true;
                opt.Password.RequireNonAlphanumeric = true;
                opt.Password.RequireUppercase = true;
                opt.Password.RequiredLength = 4;

                opt.User.RequireUniqueEmail = true;
            }
        ).AddEntityFrameworkStores<DataContext>();

        services.AddLogging();

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddNegotiate()
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Token:Key"])),
                    ValidIssuer = config["Token:Issuer"],
                    ValidateIssuer = false,
                    ValidateAudience = false
                };

                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];

                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
                        {
                            context.Token = accessToken;
                        }

                        return Task.CompletedTask;
                    },
                };
            });


        services.ConfigureApplicationCookie(options =>
        {
            options.AccessDeniedPath = "/user/access_denied";
            options.Cookie.Name = "leo-coin";
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
            options.LoginPath = "/user/login";
            options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
            options.SlidingExpiration = true;
        });

        return services;
    }
}

Solution

  • I just found out why I was getting null from the claimsPrincipal. I had to replace : AddIdentity<AppUser, IdentityRole> with AddIdentityCore and then add AddRoles. The code of the identity extension should look like this :

    public static IServiceCollection AddIdentityServices(this IServiceCollection services,
                IConfiguration config)
            {
                services.AddIdentityCore<AppUser>(opt =>
                {
                    opt.Password.RequireNonAlphanumeric = false;
                })
                    .AddRoles<IdentityRole>()
                    .AddRoleManager<RoleManager<IdentityRole>>()
                    .AddEntityFrameworkStores<DataContext>();
    
                services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                    .AddJwtBearer(options =>
                    {
                        options.TokenValidationParameters = new TokenValidationParameters
                        {
                            ValidateIssuerSigningKey = true,
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding
                                .UTF8.GetBytes(config["Token:Keys"])),
                            ValidateIssuer = false,
                            ValidateAudience = false
                        };
    
                    });
    
                services.AddAuthorization(opt =>
                {
                    opt.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin"));
                    opt.AddPolicy("ModeratePhotoRole", policy => policy.RequireRole("Admin", "Moderator"));
                });
    
                return services;
            }
        }