7
votes

I created a web api that uses JWT tokens for authorization with a role based policy (based on this article). The user logs in generates a token that is used for authorization. I successfully generate the token but when I start to use it to access restricted API actions with it it doesn't work and keeps giving me the 401 HTTP error (I cant even debug considering the action call doesn't trigger). What am I doing wrong?.

Classes:

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.AddControllers();
        services.AddScoped<ICountriesService, CountriesService>();
        services.AddScoped<ICompanyService, CompanyService>();
        services.AddScoped<IPlaneServices, PlaneService>();
        services.AddScoped<IPlaneTypeService, PlaneTypeService>();
        services.AddScoped<ICitiesService, CitiesService>();
        services.AddScoped<IAirfieldService, AirfieldService>();
        services.AddScoped<ITicketTypeService, TicketTypeService>();
        services.AddScoped<IFlightService, FlightService>();
        services.AddScoped<ILuxuryService, LuxuryService>();
        services.AddScoped<IUserService, UserService>();

        // Register the Swagger generator, defining 1 or more Swagger documents
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "My API", Version = "v1" });


            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n 
                  Enter 'Bearer' [space] and then your token in the text input below.
                  \r\n\r\nExample: 'Bearer 12345abcdef'",
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.ApiKey,
                Scheme = "Bearer"
            });

            c.AddSecurityRequirement(new OpenApiSecurityRequirement()
          {
            {
              new OpenApiSecurityScheme
              {
                    Reference = new OpenApiReference
                      {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                      },
                      Scheme = "oauth2",
                      Name = "Bearer",
                      In = ParameterLocation.Header,

                    },
                    new List<string>()
                  }
            });
            //        var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
            //c.IncludeXmlComments(xmlPath);
        });

        services.AddAutoMapper(cfg => cfg.AddProfile<Mapper.Mapper>(),
                          AppDomain.CurrentDomain.GetAssemblies());

        services.AddDbContext<FlightMasterContext>();


        services.AddCors();

        var secret = Configuration.GetValue<string>(
            "AppSettings:Secret");

        var key = Encoding.ASCII.GetBytes(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
            };
        });



    }

    // 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();
        }

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();

        // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
        // specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            c.RoutePrefix = string.Empty;
        });



        app.UseHttpsRedirection();

        app.UseRouting();

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

        // global cors policy
        app.UseCors(x => x
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());

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

        });
    }
}

The controller:

[Route("api/[controller]")]
[ApiController]
[Authorize]
public class AaTestController : ControllerBase
{

    private FlightMasterContext db { get; set; }

    private IUserService _userService;

    public AaTestController(FlightMasterContext db, IUserService userService)
    {
        this.db = db;
        _userService = userService;
    }

    [AllowAnonymous]
    [HttpPost("authenticate")]
    public IActionResult Authenticate([FromBody]AuthenticateModel model)
    {
        var user = _userService.Authenticate(model.Username, model.Password);

        if (user == null)
            return BadRequest(new { message = "Username or password is incorrect" });

        return Ok(user);
    }
    //DOESNT TRIGGER
    [Authorize(Roles = Role.Admin)]
    [HttpGet]
    public IActionResult GetAll()
    {
        var users = _userService.GetAll();
        return Ok(users);
    }

    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        // only allow admins to access other user records
        var currentUserId = int.Parse(User.Identity.Name);
        if (id != currentUserId && !User.IsInRole(Role.Admin))
            return Forbid();

        var user = _userService.GetById(id);

        if (user == null)
            return NotFound();

        return Ok(user);
    }
}

Service used for authentication and authorization:

public interface IUserService
{
    User Authenticate(string username, string password);
    IEnumerable<User> GetAll();
    User GetById(int id);
}

public class UserService : IUserService
{
    // users hardcoded for simplicity, store in a db with hashed passwords in production applications
    private List<User> _users = new List<User>
    {
        new User { Id = 1, FirstName = "Admin", LastName = "User", Username = "admin", Password = "admin", Role = Role.Admin },
        new User { Id = 2, FirstName = "Normal", LastName = "User", Username = "user", Password = "user", Role = Role.User }
    };






    public User Authenticate(string username, string password)
    {
        var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);



        var secret = "THIS IS Ughjgjhgjhghgighiizgzigiz";



        // return null if user not found
        if (user == null)
            return null;

        // authentication successful so generate jwt token
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(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);

        return user.WithoutPassword();
    }

    public IEnumerable<User> GetAll()
    {
        return _users.WithoutPasswords();
    }

    public User GetById(int id)
    {
        var user = _users.FirstOrDefault(x => x.Id == id);
        return user.WithoutPassword();
    }
}

enter image description here

1

1 Answers

34
votes

Those methods should be called in reversed order:

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

First middleware should authenticate user, and only then next one - authorize. Unfortunately not all MS docs pay attention on this detail.