APIs are the backbone of today’s digital ecosystem, powering mobile apps, SaaS platforms, IoT devices, and enterprise systems. But with their popularity comes a major challenge: security . According to OWASP, APIs are one of the top attack vectors for cybercriminals, often exploited through broken authentication, weak authorization, or data exposure.

In this article, we’ll take a practical, end-to-end journey through securing an ASP.NET Core Web API using JWT authentication and role-based authorization , with a real-world example: a Bookstore API .

Why API Security Matters

Imagine a bookstore API that exposes endpoints to manage books, authors, and sales. Without security:

That’s why API security is non-negotiable.

Common API Security Threats

Before jumping into implementation, let’s recall the top threats (from OWASP API Top 10):

  1. Broken Authentication: Weak login mechanisms or missing tokens.

  2. Broken Authorization: Users accessing data or endpoints they shouldn’t.

  3. Injection Attacks (SQL, NoSQL, Command): Malicious input executed by the system.

  4. Sensitive Data Exposure: Sending unencrypted or excessive data.

  5. Rate Limiting & DoS Attacks – Flooding APIs with requests.

  6. Improper Logging & Monitoring – No visibility into attacks.

Best Practices for API Security

To mitigate these threats, adopt a defense-in-depth approach:

Implementation: Securing a Bookstore API with JWT

We’ll now build a secure Bookstore API in ASP.NET Core Web API with:

Step 1. Create Project

  
    dotnet new webapi -n BookstoreApi
cd BookstoreApi
  

Step 2. Add NuGet Package

  
    dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
  

Step 3. Configure Authentication & Authorization ( Program.cs )

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

var builder = WebApplication.CreateBuilder(args);

// Add Authentication with JWT
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://api.bookstore.com",  
            ValidAudience = "https://api.bookstore.com"
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes("SuperSecretKey12345")) // use Key Vault in production
        };
    });

// Authorization Policies
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});

builder.Services.AddControllers();

var app = builder.Build();

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

app.MapControllers();

app.Run();
  

Step 4. Create Auth Controller ( Controllers/AuthController.cs )

  
    using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace BookstoreApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        [HttpPost("login")]
        public IActionResult Login(string username, string password)
        {
            // Demo validation (replace with ASP.NET Identity or DB check in production)
            if (username == "admin" && password == "password")
            {
                var claims = new[]
                {
                    new Claim(ClaimTypes.Name, username),
                    new Claim(ClaimTypes.Role, "Admin")
                };

                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SuperSecretKey12345"));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

                var token = new JwtSecurityToken(
                    issuer: "https://api.bookstore.com",
                    audience: "https://api.bookstore.com",
                    claims: claims,
                    expires: DateTime.Now.AddMinutes(30),
                    signingCredentials: creds);

                return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
            }

            return Unauthorized();
        }
    }
}
  

Step 5. Create Secure Data Controller ( Controllers/SecureDataController.cs )

  
    using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace BookstoreApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class SecureDataController : ControllerBase
    {
        // Accessible by any authenticated user
        [HttpGet("books")]
        [Authorize]
        public IActionResult GetBooks() =>
            Ok("List of available books. (Authenticated users only)");

        // Accessible only by Admin role
        [HttpGet("admin/reports")]
        [Authorize(Policy = "AdminOnly")]
        public IActionResult GetAdminReports() =>
            Ok("Sensitive sales reports. (Admins only)");
    }
}
  

Testing the API

  1. Log in to get a JWT token

          
            POST https://api.bookstore.com/api/auth/login
    Body: { "username": "admin", "password": "password" }
          
        

    Response

          
            { "token": "eyJhbGciOiJIUzI1NiIsIn..." }
          
        
  2. Access books endpoint. (user-protected)

          
            GET https://api.bookstore.com/api/securedata/books
    Authorization: Bearer <token>
          
        
  3. Access admin reports endpoint (admin-only)

          
            GET https://api.bookstore.com/api/securedata/admin/reports
    Authorization: Bearer <token>
          
        

Enhancing Security Further

Conclusion

API security is not just about authentication; it’s a multi-layered defense strategy. In this article, we:

With these practices, your ASP.NET Core Web APIs will be secure, reliable, and production-ready.