1

I'm making a small new project using .net core 2.0 and jwt bearer authentication (https://github.com/aspnet/Security)

Here is my Startup.cs

        /// <summary>
        ///     This method gets called by the runtime. Use this method to add services to the container.
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
        {
            // Add entity framework to services collection.
            var sqlConnection = Configuration.GetConnectionString("SqlServerConnectionString");
            services.AddDbContext<RelationalDatabaseContext>(
                options => options.UseSqlServer(sqlConnection, b => b.MigrationsAssembly(nameof(Main))));

            // Injections configuration.
            services.AddScoped<IUnitOfWork, UnitOfWork>();
            services.AddScoped<DbContext, RelationalDatabaseContext>();
            services.AddScoped<IEncryptionService, EncryptionService>();
            services.AddScoped<IIdentityService, IdentityService>();
            services.AddScoped<ITimeService, TimeService>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            // Requirement handler.
            services.AddScoped<IAuthorizationHandler, SolidAccountRequirementHandler>();
            services.AddScoped<IAuthorizationHandler, RoleRequirementHandler>();

            // Load jwt configuration from setting files.
            services.Configure<JwtConfiguration>(Configuration.GetSection(nameof(JwtConfiguration)));
            services.Configure<ApplicationSetting>(Configuration.GetSection(nameof(ApplicationSetting)));

            // Build a service provider.
            var serviceProvider = services.BuildServiceProvider();
            var jwtBearerSettings = serviceProvider.GetService<IOptions<JwtConfiguration>>().Value;

            // Cors configuration.
            var corsBuilder = new CorsPolicyBuilder();
            corsBuilder.AllowAnyHeader();
            corsBuilder.AllowAnyMethod();
            corsBuilder.AllowAnyOrigin();
            corsBuilder.AllowCredentials();

            // Add cors configuration to service configuration.
            services.AddCors(options => { options.AddPolicy("AllowAll", corsBuilder.Build()); });
            services.AddOptions();

            // This can be removed after https://github.com/aspnet/IISIntegration/issues/371
            var authenticationBuilder = services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            });

            authenticationBuilder.AddJwtBearer(o =>
            {
                // You also need to update /wwwroot/app/scripts/app.js
                o.Authority = jwtBearerSettings.Authority;
                o.Audience = jwtBearerSettings.Audience;
                o.RequireHttpsMetadata = false;

                o.SecurityTokenValidators.Clear();
                o.SecurityTokenValidators.Add(new JwtBearerValidator());

                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 500;
                        c.Response.ContentType = "text/plain";
                        if ("dev".Equals(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")))
                        {
                            // Debug only, in production do not share exceptions with the remote host.
                            return c.Response.WriteAsync(c.Exception.ToString());
                        }
                        return c.Response.WriteAsync("An error occurred processing your authentication.");
                    }
                };
            });

            #region Mvc builder

            // Construct mvc options.
            var mvcBuilder =
                services.AddMvc(mvcOptions =>
                {
                    //only allow authenticated users
                    var policy = new AuthorizationPolicyBuilder()
                        .RequireAuthenticatedUser()
                        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                        .AddRequirements(new SolidAccountRequirement())
                        .Build();

                    mvcOptions.Filters.Add(new AuthorizeFilter(policy));
                });

            // Add json configuration/
            mvcBuilder.AddJsonOptions(options =>
            {
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });

            #endregion
        }

        /// <summary>
        ///     This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="serviceProvider"></param>
        public void Configure(IApplicationBuilder app,
            IHostingEnvironment env,
            ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
        {
            // Enable logging.
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            // Use JWT Bearer authentication in the system.
            app.UseAuthentication();

            // Enable cors.
            app.UseCors("AllowAll");

            // Enable MVC features.
            app.UseMvc();
        }

With these configurations, jwt has been enabled in my web application. But, there is one thing I'm currently facing with:

  • With API doesn't require authentication (placed under [AllowAnonymous] attribute), if I pass Authorization header in my request, OnAuthenticationFailed event will be raised (due to no token is detected).

My question is: How can I make my jwt authentication automatically ignore methods or controller which is marked as AllowAnonymous ?

Thank you,

Redplane
  • 2,971
  • 4
  • 30
  • 59

4 Answers4

1

Instead of using OnAuthenticationFailed, try putting it in OnChallenge:

o.Events = new JwtBearerEvents()
{
    OnChallenge = c =>
    {
        c.HandleResponse();
        c.Response.StatusCode = 500;
        c.Response.ContentType = "text/plain";
        return c.Response.WriteAsync("An error occurred processing your authentication.");
    }
};
  • This event does not fire in this particular case. – Joerg Krause Aug 22 '18 at 19:11
  • I had exactly the same situation as the original question of this post. It took me days to find this solution and it works perfectly for my two projects. In what particular case it doesn't work? Please state that or give your solution instead of down voting mine. I was just trying to help. This's my first answer on stackoverflow and now you have discouraged me. – Man Wai Lorentz Yip Aug 23 '18 at 13:09
  • ManWaiLorentzYip, you shouldn't be discouraged by internet down votes. If you let it get to you, you will get discouraged a lot. Continue to help and provide info and we will all learn from it. I have the same prob in this thread and your sugggestion seems to work but I have no clue why. I have no clue why putting the code in OnChallenge vs OnAuthenticationFailed makes such a different as all i was trying to do is catch auth failures and map it to my own response. The documentation is unhelpful, do you know what these events actually do? Thanks – Bayo O Aug 25 '18 at 21:09
  • 1
    @BayoO'liliJason', thank you so much for your encouragement. Actually what disappointed me is that someone just down vote my solution without trying. Anyway, I also found that the asp.net core document wasn't quite helpful. Then I found some clues in the **The JwtBearerHandler HandleUnauthorisedAsync method** section of [this](https://andrewlock.net/a-look-behind-the-jwt-bearer-authentication-middleware-in-asp-net-core/), – Man Wai Lorentz Yip Aug 27 '18 at 06:58
  • 1
    and line **215** of the [source code](https://github.com/aspnet/Security/blob/a0b704efbdf924099de908c57087de49c9d12d17/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs). So **HandleChallengeAsync** seems to be the place where status code 401 is set when authentication fails. That's why I tried **OnChallenge** and it worked. To be honest, I don't know if it is the right way to do it but at least it solves my problem. Hope this helps. – Man Wai Lorentz Yip Aug 27 '18 at 07:04
  • Thank you!! It is starting to make a bit of sense now after going through the src. I will need to go over it many more times. I guess its the name that keeps throwing me off. – Bayo O Sep 10 '18 at 14:13
0

I think , it's because you are adding two authentication at the same time, one with jwt and other one with this code.

 var mvcBuilder =
                services.AddMvc(mvcOptions =>
                {
                    //only allow authenticated users
                    var policy = new AuthorizationPolicyBuilder()
                        .RequireAuthenticatedUser()
                        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                        .AddRequirements(new SolidAccountRequirement())
                        .Build();

                    mvcOptions.Filters.Add(new AuthorizeFilter(policy));
                });

Keep just the jwt and if you want to add policies you can do it like this example

services.AddAuthorization(options =>
                {
                    options.AddPolicy("CreateUser", policy => policy.AddRequirements(
                    new UserLevelRequirement("Admin")
                ));
                })

You don't to configure twice the authorization.

SamazoOo
  • 325
  • 3
  • 7
0

OnAuthenticationFailed event will allows be raised.But your API will return 406 Not Acceptable because this event will set the Response ContentType to "text/plain";. You can change your code c.Response.ContentType = "text/plain"; to c.Response.ContentType = "application/json";.

baakal
  • 146
  • 1
  • 1
  • 5
0

I found the solution here https://github.com/aspnet/Security/issues/1488

The call to AddAuthorization must be on the MvcCoreBuilder, not the service collection.

services
    .AddMvcCore()
    .AddAuthorization(...)

The drawback is that AddMvcCore() does not add all services that AddMvc() adds. Look at the source code of AddMvc() for the additional services to add.

EDIT
The above still does not work for me.

You can try [OverrideAuthentication] instead

Softlion
  • 12,281
  • 11
  • 58
  • 88