简体   繁体   中英

Dotnet core webapi returns 401 Unauthorized with JWT authentication

I am using HTML/Javascript in VS Code and using Dot net core 3.1 in Visual Studio 2019 for Web API development. Also using IIS on Windows 10 professional to test the API.

  1. Developed login page with following code. Once user enters userid and password, clicks on login button, a web api "TestAuthService" is invoked.

    function fnlogin() {
        const uname = document.getElementById('uname').value;
        const pwd = document.getElementById('pwd').value;

        const logindata = {
            username: uname,
            password: pwd
        }
        const loginurl = 'http://localhost:8091/api/Auth/Login';
        authenticate(loginurl, logindata);        
    }
        
    async function authenticate(loginurl, logindata) {
        console.log(logindata)
        const response = await fetch(loginurl , {
            method: "POST",
            mode: "cors",
            body: JSON.stringify(logindata),
            headers: { "Content-type" : "application/json, charset=UTF-8"}
        });
        const rdata = await response.json();
        console.log(rdata);
        if (!rdata.success) {
            document.getElementById("loginMessage").innerHTML = rdata.message;
            return;
        }

        const inMemoryToken = rdata.data
        localStorage.setItem('user', JSON.stringify(rdata));
        window.location.href = "http://127.0.0.1:5500/Weatherinfo.html";

    }
  1. The API TestAuthService is published in IIS on localhost:8091. On successful login, a JWT is returned to the javascript. This works properly. The JWT is stored in localStorage in chrome browser by Javascript. The auth controller code is as follows:
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly IAuthRepository _authRepo;
        public AuthController(IAuthRepository authRepo)
        {
            _authRepo = authRepo;

        }

        [HttpPost("Register")]
        public async Task<ActionResult<ServiceResponse<int>>> Register(UserRegisterDto request)
        {
            var response = await _authRepo.Register(
                new User { Username = request.Username }, request.Password
            );

            if (!response.Success)
            {
                return BadRequest(response);
            }

            return Ok(response);
        }

        [HttpPost("Login")]
        public async Task<ActionResult<ServiceResponse<string>>> Login(UserLoginDto request)
        {
            var response = await _authRepo.Login(
                request.Username, request.Password
            );

            if (!response.Success)
            {
                return BadRequest(response);
            }

            return Ok(response);
        }

The AuthRepository code is as follows:

    public class AuthRepository : IAuthRepository
    {
        private readonly AppDbContext _context;
        private readonly IConfiguration _configuration;
        public AuthRepository(AppDbContext context, IConfiguration configuration)
        {
            _configuration = configuration;
            _context = context;

        }

        public async Task<ServiceResponse<string>> Login(string username, string password)
        {
            var response = new ServiceResponse<string>();
            var user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(username.ToLower()));
            if (user == null)
            {
                response.Success = false;
                response.Message = "User not found.";
            }
            else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
            {
                response.Success = false;
                response.Message = "Wrong password.";
            }
            else
            {
                response.Data = CreateToken(user);
            }

            return response;
        }

        public async Task<ServiceResponse<User>> Register(User user, string password)
        {
            ServiceResponse<User> response = new ServiceResponse<User>();
            if (await UserExists(user.Username))
            {
                response.Success = false;
                response.Message = "User already exists.";
                return response;
            }

            CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt);

            user.PasswordHash = passwordHash;
            user.PasswordSalt = passwordSalt;

            _context.Users.Add(user);
            await _context.SaveChangesAsync();
            response.Data = user;
            return response;
        }

        public async Task<bool> UserExists(string username)
        {
            if (await _context.Users.AnyAsync(x => x.Username.ToLower().Equals(username.ToLower())))
            {
                return true;
            }
            return false;
        }

        private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                passwordSalt = hmac.Key;
                passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            }
        }

        private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
            {
                var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
                for (int i = 0; i < computedHash.Length; i++)
                {
                    if (computedHash[i] != passwordHash[i])
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        private string CreateToken(User user)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username)
            };

            var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));

            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

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

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokendDescriptor);

            return tokenHandler.WriteToken(token);
        }
  1. If the login is successful, the page Weatherinfo.html is displayed, This page has button "Get Weather data", which when clicked, invokes another web api "weatherforecast"
    <button type="button" onclick="return getWeather();">Get Weather data</button>

    <table id="weatherdata">
        <thead>

        </thead>
        <tbody id="weatherdatalist">

        </tbody>
    </table>

    <script>

        async function getWeather() {
            const url = 'http://localhost:5861/weatherforecast';
            const localstorage_user = JSON.parse(localStorage.getItem('user'));
            const inMemToken = localstorage_user.data
            console.log(inMemToken)

            /*
            const response = await fetch(url);
            */
            const response = await fetch(url, {
                headers: {
                    Authorization: 'Bearer ${inMemToken}'
                }
            });
            
            const data = await response.json();
            console.log(data)

            display(data);
        }

        function display(data) {
            let tab = "";

            data.forEach(element => {
                tab += `<tr> 
                    <td > ${element.date} </td>
                    <td> ${element.temperatureC} </td>
                    <td> ${element.temperatureF} </td>
                    <td> ${element.summary} </td>
                    </tr>`;
            });
            document.getElementById("weatherdatalist").innerHTML = tab;
        }
        


    </script>
  1. The weatherforecast api has following code

Startup class

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(o => o.AddPolicy("AllowOrigins", builder =>
            {
                builder.WithOrigins("http://localhost:5500", "http://127.0.0.1:5500")
                 .AllowAnyMethod()
                 .AllowAnyHeader();
            }));

            services.AddControllers();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = false,
                        ValidateIssuer = false,
                        ValidateAudience = false

                    };

                });

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseAuthentication();
            app.UseRouting();
            
            app.UseCors("AllowOrigins");

            app.UseAuthorization();

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

The controller is as follows

   [ApiController]
    [Route("[controller]")]
    [Authorize]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }

  1. The issue is the weatherforcast api fails with HTTP error 401 (Unauthorized)

I am not sure if I am passing the JWT correctly in the code below

 const response = await fetch(url, {
            headers: {
                Authorization: 'Bearer ${inMemToken}'
            }
        });

The issue seems to be resolved now. Corrected the fetch api call as

            const response = await fetch(url, {
                headers: {
                    "Authorization": `Bearer ${inMemToken}`
                }
            });

``

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM