Securing ASP.NET Core APIs with Identity and JWT

In today's web development landscape, securing APIs is crucial. ASP.NET Core offers robust features for implementing authentication and authorization. This article will guide you through using ASP.NET Core Identity with JWT (JSON Web Tokens) to secure your APIs effectively.

Setting Up ASP.NET core Identity

First, ensure you have a new ASP.NET Core Web API project. You can create one using the .NET CLI

dotnet new webapi -n SecureApi
cd SecureApi

Add the necessary NuGet packages for ASP.NET Core Identity and Entity Framework Core (EF Core) for your database interactions.

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

Configuring Identity

Next, set up Identity in your Startup.cs or Program.cs (for .NET 6 and later).

public class ApplicationUser : IdentityUser { }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
        : base(options) { }
}
// In ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
    });
    services.AddControllers();
}

Update appsettings.json with your JWT configuration.

"Jwt": {
  "Issuer": "yourdomain.com",
  "Audience": "yourdomain.com",
  "Key": "YourSecretKeyHere"
}

Creating user registration and login endpoints

Now, let’s create endpoints for user registration and login to issue JWT tokens.

[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IConfiguration _configuration;
    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IConfiguration configuration)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _configuration = configuration;
    }
    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterModel model)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            return Ok(new { Result = "User created successfully" });
        }
        return BadRequest(result.Errors);
    }
    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginModel model)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
        {
            var token = GenerateJwtToken(user);
            return Ok(new { Token = token });
        }
        return Unauthorized();
    }
    private string GenerateJwtToken(ApplicationUser user)
    {
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: creds);
        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
public class RegisterModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}
public class LoginModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

Securing API endpoints

With authentication and token generation in place, secure your API endpoints by adding the [Authorize] attribute.

[ApiController]
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
    [HttpGet]
    [Authorize]
    public IActionResult GetValues()
    {
        return Ok(new string[] { "Value1", "Value2" });
    }
}

Testing the API

  1. Register a user: Send a POST request to api/account/register with a JSON body containing email and password.
    {
        "email": "[email protected]",
        "password": "Password123!"
    }
    
  2. Login: Send a POST request to api/account/login with the same email and password to receive a JWT token.
    {
        "email": "[email protected]",
        "password": "Password123!"
    }
    
  3. Access secured endpoint: Use the received JWT token in the Authorization header as Bearer {token} to access api/values.

Conclusion

By following these steps, you can implement robust authentication and authorization in your ASP.NET Core applications using Identity and JWT tokens. This approach ensures your APIs are secure and can efficiently manage user authentication and authorization.