User doesn't seem authenticated in the pipline inside`Use` in dotnetcore 2.0
Asked Answered
M

3

4

I am trying to provide an ActiveUser property to Serilog.
Unfortunately I cannot seem to find the correct spot to check for the current user.

In the below code httpContext.User.Identity.IsAuthenticated is always false?

But only when logging in with the bearer token

  • The bearer token login is working correctly insofar as the user is authenticated to the controller methods, and the user needs to belong to the correct roles in order to be authenticated. Though the user name is not correctly set - the claims are present, and IsAuthenticated is set to true.
  • If I use the cookie login, the user is set correctly, and the claims are set correctly, and the Serilog works correctly. This is true whether using the bearer token or a cookie to call in. Once the user is logged in with a cookie it always works.

When the bearer token is validated, the user is not immediately set?

The project is aspnetcore 2.0

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{

    ... (other configuration items)

    app.UseIdentityServer();
    app.UseAuthentication();

    app.Use(async (httpContext, next) =>
    {
        // HERE IsAuthenticated IS ALWAYS FALSE
        // HERE THE CLAIMS ARE ALWAYS EMPTY, UNLESS
        // I LOGIN USING THE COOKIE AS WELL - THEN IT WORKS
        var userName = httpContext.User.Identity.IsAuthenticated 
            ? httpContext.User.GetClaim("name")
            : "(unknown)";
        LogContext.PushProperty(
            "ActiveUser",
            !string.IsNullOrWhiteSpace(userName)
                 ? userName
                 : "(unknown)");
        await next.Invoke();
    });

    app.UseMvc(
        routes =>
        {
            routes.MapRoute(
                "default",
                "{controller=Home}/{action=Index}/{id?}");
        });

In my controller method, the User is set correctly, and is authenticated.

[Authorize]
[HttpGet("user")]
public object UserDetail()
{
    // HERE THE CLAIMS ARE SET, IsAuthenticated IS ALWAYS TRUE
    // AS THE USER MUST BE AUTHENTICATED TO GET HERE
    Debug.Assert(this.User.Identity.IsAuthenticated == true)

edit
Digging into the problem further it would appear that the JWTBearer token is validated AFTER my middleware has already executed. The middleware needs to execute AFTER the token is validated.

TL;DR
(the full configuration)

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();
    app.UseIdentityServer();
    app.UseAuthentication();
    app.Use(async (httpContext, next) =>
                    {
                        var userName = httpContext.User.Identity.IsAuthenticated 
                        ? httpContext.User.GetClaim("email")
                        : "(unknown)";
                        LogContext.PushProperty("ActiveUser", !string.IsNullOrWhiteSpace(userName) ? userName : "(unknown)");
                        await next.Invoke();
                    });

    app.UseMvc(
        routes =>
        {
            routes.MapRoute(
                "default",
                "{controller=Home}/{action=Index}/{id?}");
        });

}

(more configuration)

   public void ConfigureServices(IServiceCollection services)
   {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddAuthentication()
            .AddOpenIdConnect(
                o =>
                {
                    o.Authority = "https://localhost:44319";
                    o.ClientId = "api";
                    o.ClientSecret = "secret";
                    o.RequireHttpsMetadata = false;
                    o.ResponseType = "code id_token token";
                    o.GetClaimsFromUserInfoEndpoint = true;
                })
            .AddJwtBearer(
                o =>
                {
                    o.Authority = "https://localhost:44319";
                    o.Audience = "api";
                    o.RequireHttpsMetadata = false;
                    //o.SaveToken = true;
                });

        services.AddMemoryCache();
        services.AddIdentity<ApplicationUser, ApplicationRole>(
                x =>
                {
                    x.Password.RequireNonAlphanumeric = false;
                    x.Password.RequireUppercase = false;
                })
            .AddEntityFrameworkStores<FormWorkxContext>()
            .AddDefaultTokenProviders()
            .AddIdentityServer();

        // NB
        services.Configure<IdentityOptions>(
            options =>
            {
                options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role;
                options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
            });

        services.ConfigureApplicationCookie(
            options =>
            {
                options.LoginPath = "/login";
                options.LogoutPath = "/logout";
                options.Events.OnRedirectToLogin = this.ProcessStatusCodeResponse;
            });

        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApis())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>();

        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc(
                _ =>
                {
                    _.Filters.Add(
                        new AuthorizeFilter(
                            new AuthorizationPolicyBuilder(
                                    JwtBearerDefaults.AuthenticationScheme,
                                    IdentityConstants.ApplicationScheme)
                                .RequireAuthenticatedUser()
                                .Build()));
                    _.Filters.Add(new ExceptionFilter());
                    _.ModelBinderProviders.Insert(0, new PartyModelBinderProvider());
                    _.ModelBinderProviders.Insert(0, new DbGeographyModelBinder());
                    _.ModelMetadataDetailsProviders.Add(new KeyTypeModelMetadataProvider());
                })
            .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>())
            .AddJsonOptions(json => json.SerializerSettings.Converters.Add(new DbGeographyJsonConverter()));
    }
Management answered 18/9, 2017 at 8:5 Comment(10)
Show configure services method.Empathize
When you say "user is set correctly" you mean in the Identity.Name right? if yes, I have faced the same issue it shows me the name but doesn't set the IsAuthenticated to true. When I change my login code it then set the login to true. How exactly are you logging in the user.Thursby
Your Configure method is only run once on app startup, at which point there is no user or even http context. I haven't used Serilog (yet) but you'd likely need to include the user as part of the data when actually logging something. LogContext sounds like it would also be a singleton and not related to the particular users session.Come
@Neville, sorry I should have been specific, none of the claims are set, the user is not set. The user is not present at all. Later on in the pipeline (in the actual controller method) it's all set correctly. Unfortunately the login is complex - using JWTToken's and identityserver. I can post the complete configuration - I just wanted to keep it as brief as possible.Management
@rory_za, the add method takes a lambda that is re-executed on the pipeline for every request. So yes, when the lambda is first passed in there is no context, but the lambda receives both the current context, and the next handler in the sequence.Management
so are you validating the jwt manually or is the login after the jwt automated?Thursby
@Neville - I have updated the question, not sure where to go from here. The authentication is not correct. These bearer tokens are a nightmare - it's taken me weeks to get to this point, and they're still wrong.Management
once again just to confirm, you do see an output in User.Identity.Name right?Thursby
@Neville - no I don't - the claims are all there, but the user name is not - not set correctly by identity server. IsAuthenticated is true in the controller method - called from a bearer token, but not in the pipeline method (Serilog)Management
@Neville - I am using identityserver to validate tokensManagement
T
0

I have replicated this issue when logging in using a principal set up as follows:

var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));

Then I login with SignInAsync. This too leads to User.Identity.Name having a value but the User.Identity.IsAuthenticated not being set to true.

Now when I add the authenticationType parameter to ClaimsIdentity like this:

var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "local"));

The IsAuthenticated is now set to true.

I am not entirely sure how your sign in would work and you could mention this authenticationType somewhere or you could pass it along while creating the JWT. That is the way I had done it.

Update ok just noticed your comment about the Name not shown either, but you can still try setting the authenticationType. Also as far as your claims are right, you should be able to extract the principle using AuthenticateAsync. Once you can access the principle from the Context.User object, you can always customize the an authentication scheme to force in the principal.

Update 2 In your case, inside your AddJwtBearer, try including this:

o.Events.OnTokenValidated = async (context) => {
    context.Principal = new ClaimsPrincipal(new ClaimsIdentity(context.Principal.Claims, "local"));
};
Thursby answered 19/9, 2017 at 5:40 Comment(8)
It seems to be a problem with JWTBearer authentication in the the context user is not set when the token is validated - I am not sure what hook to use to update the principle when the token is validated.Management
is it a build in authentication scheme? can you find a way to extend it?Thursby
it's the AddJwtBearer that's built into dotnetcore. I think I am going to try and ask another simpler question. There are too many red herrings in this one.Management
yes in the build in jwt i have seen people setting events such as this quite oftenThursby
The event is triggered, and I think you're close, it would solve my problem, the issue now is that the bloody App.Use(... is called BEFORE the token is validated.Management
I cannot seem to find a way to execute any middleware AFTER the token is validated.Management
I am going to accept your answer as I has narrowed down the issue to a pipeline timing problem. I will ask another question about the token validation.Management
you can always create a complete middleware and use it before the mvc that would check and fix this issueThursby
V
2

Copying my answer from your other related question in case anyone comes across this and wonders what's going on:

Since you have multiple authentication schemes registered and none is the default, authentication does not happen automatically as the request goes through the pipeline. That's why the HttpContext.User was empty/unauthenticated when it went through your custom middleware. In this "passive" mode, the authentication scheme won't be invoked until it is requested. In your example, this happens when the request passes through your AuthorizeFilter. This triggers the JWT authentication handler, which validates the token, authenticates and sets the Identity, etc. That's why the User is populated correctly by the time it gets to your controller action.

Vitellin answered 19/9, 2017 at 20:56 Comment(0)
T
0

I have replicated this issue when logging in using a principal set up as follows:

var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));

Then I login with SignInAsync. This too leads to User.Identity.Name having a value but the User.Identity.IsAuthenticated not being set to true.

Now when I add the authenticationType parameter to ClaimsIdentity like this:

var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "local"));

The IsAuthenticated is now set to true.

I am not entirely sure how your sign in would work and you could mention this authenticationType somewhere or you could pass it along while creating the JWT. That is the way I had done it.

Update ok just noticed your comment about the Name not shown either, but you can still try setting the authenticationType. Also as far as your claims are right, you should be able to extract the principle using AuthenticateAsync. Once you can access the principle from the Context.User object, you can always customize the an authentication scheme to force in the principal.

Update 2 In your case, inside your AddJwtBearer, try including this:

o.Events.OnTokenValidated = async (context) => {
    context.Principal = new ClaimsPrincipal(new ClaimsIdentity(context.Principal.Claims, "local"));
};
Thursby answered 19/9, 2017 at 5:40 Comment(8)
It seems to be a problem with JWTBearer authentication in the the context user is not set when the token is validated - I am not sure what hook to use to update the principle when the token is validated.Management
is it a build in authentication scheme? can you find a way to extend it?Thursby
it's the AddJwtBearer that's built into dotnetcore. I think I am going to try and ask another simpler question. There are too many red herrings in this one.Management
yes in the build in jwt i have seen people setting events such as this quite oftenThursby
The event is triggered, and I think you're close, it would solve my problem, the issue now is that the bloody App.Use(... is called BEFORE the token is validated.Management
I cannot seem to find a way to execute any middleware AFTER the token is validated.Management
I am going to accept your answer as I has narrowed down the issue to a pipeline timing problem. I will ask another question about the token validation.Management
you can always create a complete middleware and use it before the mvc that would check and fix this issueThursby
P
0

Authenticate the user explicitly in your custom middleware by adding the following line of code:

var result = await context.Request.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);//AuthenticationOptions.DefaultAuthenticateScheme)
            if (result.Succeeded)
            {
                //context.User.AddIdentity(result.Principal);
                context.User = result.Principal;
            }
Phono answered 15/7, 2019 at 4:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.