ASP.Net Core API always returns 401 unauthorized whenever I send a request with Bearer token included

25,736

Solution 1

I had same issue, but after moveup

app.UseAuthentication();

to before line of

app.UseAuthorization();

in

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ..
    app.UseAuthentication();
    ..
    app.UseAuthorization();
    ...
}

it worked.

Solution 2

I had this issue with dotnet core 3.1, and I was flipping every switch trying to get this to work. Eventually what ended up getting this to run was tletle's answer. Execute app.UseAuthentication() before app.UseAuthorization(). To elaborate on tletle's answer, below is the relevant code.

In Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // ...
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication(); // this one first
    app.UseAuthorization(); 
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

My authentication middleware is in an extension method I wrote that is called from the ConfigureServices() method in Startup.cs:

        public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            string issuer = configuration.GetValue<string>("Jwt:Issuer");
            string signingKey = configuration.GetValue<string>("Jwt:Key");
            byte[] signingKeyBytes = System.Text.Encoding.UTF8.GetBytes(signingKey);

            services.AddAuthentication(opt=>
            {
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options=>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidIssuer = issuer,
                    ValidateAudience = true,
                    ValidAudience = issuer,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ClockSkew = System.TimeSpan.Zero,
                    IssuerSigningKey = new SymmetricSecurityKey(signingKeyBytes)
                };
            });
        }

and the token was generated using this extension method:

public static string GenerateApiUserToken(this ApiUser user, IConfiguration configuration)
{
    string signingKey = configuration.GetValue<string>("Jwt:Key");
    string issuer = configuration.GetValue<string>("Jwt:Issuer");
    int hours = configuration.GetValue<int>("Jwt:HoursValid");
    System.DateTime expireDateTime = System.DateTime.UtcNow.AddHours(hours);

    byte[] signingKeyBytes = System.Text.Encoding.UTF8.GetBytes(signingKey);
    SymmetricSecurityKey secKey = new SymmetricSecurityKey(signingKeyBytes);
    SigningCredentials creds = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256);

    var authClaims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.Role, user.RoleName)
    };

    JwtSecurityToken token = new JwtSecurityToken(
        issuer:issuer,
        audience: issuer,
        claims: authClaims,
        expires: System.DateTime.UtcNow.AddHours(hours),
        signingCredentials:creds
    );
    JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
    string writtenToken = handler.WriteToken(token);

    return writtenToken;
}

My Controller class:

[Authorize]
[ApiController]
[Microsoft.AspNetCore.Mvc.Produces("application/json")]
[Microsoft.AspNetCore.Mvc.Route("/[controller]/values", Name="MyController")]
public class MyController : Microsoft.AspNetCore.Mvc.Controller

If the [Authorize] tag is on the controller, you should remove any that are on member methods; I left one on the method I was testing and the fix wouldn't work until I removed it.

Solution 3

Once I had the same problem after I switched from HTTP to HTTPS. It was working fine in POSTMAN with HTTP but when I switched to HTTPS it began to return 401 unauthorized

The problem was solved as soon as I added

        app.UseAuthentication();

inside

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    }

within the

public class Startup
{
}

Solution 4

It works finally, not a 100% sure why but I did headers: { Authorization: "Bearer" + " " + Bearer } in my react app. I do the equivalent in Postman and it doesn't work.

Solution 5

I had this same issue and this was what i did on postman, in the header section using bulk edit.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbmllbEBtZ2ljLmNvbToxMTExMTE6IiwibmJmIjoxNTcwNjIxMDE5LCJleHAiOjE1NzMyOTk0MTksImlhdCI6MTU3MDYyMTAxOX0.cTjhhHqf75VN0RwQvly6nppeNUkKbzQ5_ZVPFyruuKPBQ

Also remember to include the authentication filter at the top of your endpoints, which is [Authorize] in your case.

Share:
25,736
Timothy
Author by

Timothy

Updated on January 30, 2020

Comments

  • Timothy
    Timothy about 4 years

    I have an ASP .NET Core web api and I generate a JWT token for authorization purposes but whenever I make a request with Postman with Bearer token header I get 401 Unauthorized. Same when I try from my front-end that's consuming the API. When I remove Authorize everything works fine

    Tried changing Authorize in my header to //[Authorize(AuthenticationSchemes = "Bearer")] also went to jwt.io to ensure the JWT Token is valid which it is.

    //function where I generate JWT
      public   User AuthenticateAdmin(string username, string password)
            {
                var user =  _context.User.FirstOrDefault(x => x.UserName == username && x.Password == password);
    
                //return null if user is not found 
                if  (user == null) return null;
    
                //authentication successful so generate jwt token
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject= new ClaimsIdentity(new Claim[]
                    {
                        new Claim(ClaimTypes.Name, user.Id.ToString()),
                        new Claim(ClaimTypes.Role,user.Role)
                    }),
                    Expires=DateTime.UtcNow.AddDays(7),
                    SigningCredentials= new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                };
                var token = tokenHandler.CreateToken(tokenDescriptor);
                user.Token = tokenHandler.WriteToken(token);
    
                user.Password = null;
                return user;
            }
    
    //my startup.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.HttpsPolicy;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Microsoft.EntityFrameworkCore;
    using TheBackend.Models;
    using TheBackend.Helpers;
    using Microsoft.IdentityModel.Tokens;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using System.Text;
    using TheBackend.Services;
    using Microsoft.AspNetCore.Identity.UI.Services;
    using Newtonsoft.Json.Serialization;
    using Microsoft.AspNetCore.Authorization;
    
    namespace TheBackend
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.Configure<AuthMessengerOptions>(Configuration);
                var connection = @"Host=localhost;Database=PayArenaMock;Username=postgres;Password=tim";
                services.AddDbContext<PayArenaMockContext>(options => options.UseNpgsql(connection));
                services.AddTransient<IEmailSender, EmailSender>();
    
                //services.AddAuthorization(auth =>
                //{
                //    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                //        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
                //        .RequireAuthenticatedUser().Build());
                //});
                services.AddCors();
                //services.AddMvcCore()
                // .AddAuthorization() // Note - this is on the IMvcBuilder, not the service collection
                // .AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
                //services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
                //configure strongly typed settings objects
                var appSettingsSection = Configuration.GetSection("AppSettings");
                services.Configure<AppSettings>(appSettingsSection);
                //configure JWT authentication
                var appSettings = appSettingsSection.Get<AppSettings>();
                var key = Encoding.ASCII.GetBytes(appSettings.Secret);
                services.AddAuthentication(x =>
                {
                    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(x=>
                {
                    x.RequireHttpsMetadata = false;
                    x.SaveToken = true;
                    x.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey=true,
                        IssuerSigningKey= new  SymmetricSecurityKey(key),
                        ValidateIssuer=false,
                        ValidateAudience=false
                    };
                });
    
                services.AddScoped<IUserService, UserService>();
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    // 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(x => x
                 .AllowAnyOrigin()
                 .AllowAnyMethod()
                 .AllowAnyHeader());
                app.UseAuthentication();
                app.UseHttpsRedirection();
                app.UseMvc();
            }
        }
    }
    
    
    //controller
    //[Authorize(AuthenticationSchemes = "Bearer")]
        [Authorize]
        [Route("api/[controller]")]
        [ApiController]
        public class BusinessListingsController : ControllerBase
        {
            private readonly PayArenaMockContext _context;
    
            public BusinessListingsController(PayArenaMockContext context)
            {
                _context = context;
            }
    
            // GET: api/BusinessListings
            [HttpGet]
            //[AllowAnonymous]
            //[Authorize(Roles = Role.Admin)]
            public async Task<ActionResult<IEnumerable<BusinessListing>>> GetBusinessListing()
            {
    
                //var businesslisting = _context.BusinessListing.Include(b => b.CategoryNameNav);
                var businesslisting = await _context.BusinessListing.ToListAsync()
               ;
                return Ok( businesslisting);
            }
    
  • ashlar64
    ashlar64 over 3 years
    Argh...spent like a day and a half and when I did your fix it worked. I sometimes feel like I spend more time figuring out stupid issues like this than actual programming.
  • Admin
    Admin over 3 years
    I share your pain, it was about the same for me haha. Great to hear I'm helping other devs - thanks!
  • Robooto
    Robooto over 3 years
    This was exactly my issue!
  • abovetempo
    abovetempo over 3 years
    Thank you for this response it fixed my problem too!
  • Gabe
    Gabe over 2 years
    Or in my case, pay attention and don't put app.UseAuthorization twice. lol Thanks for drawing my attention to my goof!