Securing ASP.NET Core Web API with JWT Authentication and Role-Based Authorization

Introduction

Securing APIs in an ASP.NET Core Web API involves implementing authentication and authorization mechanisms to protect your resources and ensure that only authorized users can access them. In this example, I'll provide a step-by-step guide on how to secure an ASP.NET Core Web API using JWT (JSON Web Tokens) authentication and role-based authorization.

Step 1. Create a new ASP.NET Core Web API Project

You can create a new ASP.NET Core Web API project using Visual Studio or the .NET CLI.

dotnet new webapi -n ApiSecuritySecureApi
cd ApiSecuritySecureApi

Step 2. Add Required NuGet Packages

You will need to add the following NuGet packages to your project:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.AspNetCore.Identity

Step 3. Configure Identity

In the Startup.cs file, configure Identity services in the ConfigureServices method. You'll also need to configure your database connection in the appsettings.json file.

Author: Sardar Mudassar Ali Khan

public void ConfigureServices(IServiceCollection services)
{
    // ...
    
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    
    // ...
}

Step 4. Configure Authentication and JWT

Still in the Startup.cs file, configure JWT authentication in the ConfigureServices method, and use it in the Configure method.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

Author: Sardar Mudassar Ali Khan
public void ConfigureServices(IServiceCollection services)
{
    // ...

    // Configure JWT authentication
    var jwtSettings = Configuration.GetSection("JwtSettings");
    var key = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]);
    
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

    // ...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

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

    // ...
}

Step 5. Create a Controller

Create a controller with some sample endpoints that need authentication and authorization.

// Controllers/ValuesController.cs

Author: Sardar Mudassar Ali Khan
[Authorize(Roles = "Admin")] // Role-based authorization
[ApiController]
[Route("api/[controller]")]
public class ValuesController: ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok(new { message = "This is a protected resource." });
    }

    // Other endpoints
}

Step 6. Configure JWT Settings

In your appsettings.json, configure the JWT settings:

{
  "JwtSettings": {
    "SecretKey": "YourSecretKeyHere",
    "Issuer": "YourIssuer",
    "Audience": "YourAudience",
    "DurationInMinutes": 60
  },
  
}

Step 7. Generate JWT Tokens

Implement a service to generate JWT tokens when a user logs in successfully.

using Microsoft.IdentityModel.Tokens;

Author: Sardar Mudassar Ali Khan
public class TokenService
{
    private readonly IConfiguration _configuration;

    public TokenService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string GenerateToken(ApplicationUser user, IList<string> roles)
    {
        var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["JwtSettings:SecretKey"]));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.NameIdentifier, user.Id),
        };

        foreach (var role in roles)
        {
            claims.Add(new Claim(ClaimTypes.Role, role));
        }

        var token = new JwtSecurityToken(
            _configuration["JwtSettings:Issuer"],
            _configuration["JwtSettings:Audience"],
            claims,
            expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(_configuration["JwtSettings:DurationInMinutes"])),
            signingCredentials: credentials
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

Step 8. Implement Authentication

In your authentication controller, implement user login and token generation logic using the TokenService.

Author: Sardar Mudassar Ali khan

[AllowAnonymous]
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly TokenService _tokenService;

    public AuthController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        TokenService tokenService)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _tokenService = tokenService;
    }

    [Authorize]
    [HttpPost("login")]
    public async Task<IActionResult> Login(LoginDto model)
    {
        var user = await _userManager.FindByNameAsync(model.UserName);

        if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
        {
            var roles = await _userManager.GetRolesAsync(user);
            var token = _tokenService.GenerateToken(user, roles);

            return Ok(new { Token = token });
        }

        return Unauthorized();
    }
}

Step 9. Configure Roles

You can configure user roles and assign them in your SeedData or during user registration.

Step 10. Protect Endpoints

As shown in the ValuesController, you can use the [Authorize] attribute to protect your endpoints. You can also use [AllowAnonymous] for public endpoints.

Step 11. Testing

  • You can now test your secure API by obtaining a token through the login endpoint and using that token to access protected resources.
  • This example covers the basics of securing an ASP.NET Core Web API using JWT authentication and role-based authorization. You can extend and customize it to meet your specific security requirements.

Conclusion

Securing an ASP.NET Core Web API is a critical aspect of building a secure and reliable application. By implementing authentication and authorization mechanisms, such as JWT authentication and role-based authorization, you can control access to your API's resources and protect sensitive data. Here's a summary of the steps covered in this example:

  1. Create a new ASP.NET Core Web API project.
  2. Add required NuGet packages for authentication and authorization.
  3. Configure Identity services and the database connection in Startup.cs.
  4. Configure JWT authentication in Startup.cs, including token validation parameters and secret keys.
  5. Create a controller with sample endpoints that require authentication and authorization.
  6. Configure JWT settings in appsettings.json.
  7. Implement a service to generate JWT tokens for authenticated users.
  8. Create an authentication controller with login and token generation logic.
  9. Configure user roles and assign them as needed.
  10. Protect your API endpoints using [Authorize] and [AllowAnonymous] attributes.
  11. Test your secure API by obtaining tokens and accessing protected resources.

This example provides a foundation for securing your ASP.NET Core Web API, but security is an ongoing process. Be sure to stay updated with best practices and consider additional security measures, such as input validation, rate limiting, and encryption, depending on your application's requirements and threat model.


Similar Articles