Securing Your Web API
Learn how to protect your web APIs from threats using authentication, authorization, encryption, and best practices.
A Simple Analogy
Imagine a bank with different security layers:
- HTTPS lock: The front door has a strong lock (encryption)
- ID check: Guard verifies who you are (authentication)
- Permission check: Guard confirms what you're allowed to do (authorization)
- Rate limiting: Bouncer limits how many people enter per minute (DDoS protection)
- Audit trail: Cameras record everything for later review (logging)
A secure API has all these layers working together.
What Does "Securing an API" Mean?
Securing an API means protecting it from unauthorized access, data breaches, attacks, and misuse. It involves multiple layers: encryption, authentication, authorization, input validation, and monitoring.
Why Is API Security Critical?
- Data protection: Prevent theft of sensitive user data
- Compliance: Meet legal requirements (GDPR, HIPAA, PCI-DSS)
- Trust: Users trust you with their data
- Availability: Prevent attacks that take your API offline
- Brand reputation: A breach damages your credibility
- Financial: Breaches cost millions in fines and recovery
Core Security Concepts
1. Authentication (Who Are You?)
Authentication verifies the identity of a client making a request.
Common Methods:
Username/Password (Basic Auth)
// Simplest form (never use over HTTP!)
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpPost("login")]
public IActionResult Login(string username, string password)
{
if (VerifyCredentials(username, password))
{
return Ok(new { token = GenerateToken(username) });
}
return Unauthorized();
}
}
JWT (JSON Web Tokens)
// Industry standard for APIs
[HttpPost("login")]
public IActionResult Login(LoginRequest request)
{
if (ValidateUser(request.Username, request.Password))
{
var token = new JwtSecurityTokenHandler().WriteToken(
new JwtSecurityToken(
issuer: "myapp",
audience: "myapp-users",
claims: new[] { new Claim(ClaimTypes.Name, request.Username) },
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my-secret-key")),
SecurityAlgorithms.HmacSha256)
));
return Ok(new { token });
}
return Unauthorized();
}
OAuth 2.0
// Delegate to identity provider (Google, Microsoft, GitHub)
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = "your-google-client-id";
options.ClientSecret = "your-google-client-secret";
});
2. Authorization (What Can You Do?)
Authorization determines what authenticated users are allowed to access.
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")] // Only admins can delete
public IActionResult DeleteUser(int id)
{
_userService.DeleteUser(id);
return NoContent();
}
[HttpGet("{id}")]
[Authorize] // Any authenticated user can view
public IActionResult GetUser(int id)
{
return Ok(_userService.GetUser(id));
}
[HttpGet("public")]
// No [Authorize] - anyone can access
public IActionResult GetPublicData()
{
return Ok(new { message = "Public data" });
}
}
3. Encryption (Protect Data in Transit)
Always use HTTPS to encrypt data between client and server.
// Configure HTTPS requirement
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
var app = builder.Build();
app.UseHttpsRedirection(); // Force HTTPS
API Security Best Practices
1. Use HTTPS (TLS/SSL) Always
// Never send sensitive data over HTTP
// ✓ CORRECT
https://api.example.com/users/login
// ✗ WRONG
http://api.example.com/users/login
2. Input Validation (Prevent Injection)
public class CreateUserRequest
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(50, MinimumLength = 8)]
public string Password { get; set; }
}
[HttpPost]
public IActionResult CreateUser([FromBody] CreateUserRequest request)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Validated data only reaches here
var user = _userService.CreateUser(request);
return Created(nameof(GetUser), user);
}
3. Rate Limiting (Prevent Brute Force & DDoS)
// Limit requests per IP
builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
context => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.Request.Headers["X-Forwarded-For"].ToString(),
factory: partition => new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
});
app.UseRateLimiter();
4. CORS (Control Who Can Call Your API)
// Only allow specific origins
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy
.WithOrigins("https://app.example.com")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
app.UseCors("AllowFrontend");
5. Secrets Management (Don't Hardcode Keys)
// ✗ WRONG - Hardcoded secret
var apiKey = "sk-1234567890abcdef";
// ✓ CORRECT - From environment or secrets manager
var apiKey = builder.Configuration["ApiKey"];
// ✓ BETTER - Azure Key Vault in production
var keyVaultUrl = new Uri("https://myvault.vault.azure.net/");
builder.Configuration.AddAzureKeyVault(
keyVaultUrl,
new DefaultAzureCredential());
6. Logging & Monitoring
public class ApiSecurityMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ApiSecurityMiddleware> _logger;
public async Task InvokeAsync(HttpContext context)
{
var startTime = DateTime.UtcNow;
await _next(context);
// Log all API calls with security context
_logger.LogInformation(
"API Call: {Method} {Path} | User: {User} | Status: {Status} | Duration: {Duration}ms",
context.Request.Method,
context.Request.Path,
context.User.Identity?.Name ?? "Anonymous",
context.Response.StatusCode,
(DateTime.UtcNow - startTime).TotalMilliseconds);
}
}
app.UseMiddleware<ApiSecurityMiddleware>();
7. CSRF Protection (For Form Submission APIs)
// Protect POST/PUT/DELETE endpoints
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult CreateUser(UserDto dto)
{
// CSRF token is validated automatically
var user = _userService.CreateUser(dto);
return Created(nameof(GetUser), user);
}
8. API Key Management
// Check API key on protected endpoints
[HttpGet("admin/stats")]
public IActionResult GetStats([FromHeader(Name = "X-API-Key")] string apiKey)
{
if (!ValidateApiKey(apiKey))
return Unauthorized();
return Ok(_statsService.GetStats());
}
Practical Security Checklist
□ HTTPS enabled (all endpoints)
□ Authentication implemented (JWT/OAuth)
□ Authorization rules defined (roles, policies)
□ Input validation on all endpoints
□ Rate limiting enabled
□ CORS configured properly
□ Secrets stored securely (not in code)
□ Logging and monitoring active
□ Dependencies updated regularly
□ Security headers set (HSTS, CSP, X-Frame-Options)
□ Error messages don't leak info (generic 500 errors)
□ Database passwords hashed (bcrypt, Argon2)
□ SQL injection prevented (parameterized queries)
□ XSS prevented (output encoding)
□ CSRF protection enabled
Real-World Attack Scenarios & Prevention
Scenario 1: Brute Force Login Attack
Attack: Attacker tries 1000 passwords per second
Prevention: Rate limiting + account lockout
if (failedAttempts > 5)
{
account.LockUntil = DateTime.UtcNow.AddMinutes(15);
}
Scenario 2: SQL Injection
// ✗ VULNERABLE
var user = context.Users
.FromSqlInterpolated($"SELECT * FROM Users WHERE Email = '{email}'");
// ✓ SAFE - Parameterized
var user = context.Users
.FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}");
Scenario 3: Data Breach via API
Attack: Attacker gets another user's data
Prevention: Authorization checks
public User GetUser(int userId)
{
var currentUser = User.FindFirst(ClaimTypes.NameIdentifier);
if (userId != int.Parse(currentUser.Value) && !User.IsInRole("Admin"))
throw new UnauthorizedException();
return _userService.GetUser(userId);
}
Related Concepts to Explore
- OAuth 2.0 and OpenID Connect
- SAML (enterprise single sign-on)
- Certificate pinning
- Web Application Firewall (WAF)
- API gateways and security
- Encryption at rest (database encryption)
- Key rotation strategies
- Vulnerability scanning
- Penetration testing
- Security headers (HSTS, CSP, X-Content-Type-Options)
- API versioning for security updates
- Zero-trust security model
- Dependency scanning and updates
Summary
Securing your web API requires multiple layers: HTTPS encryption, strong authentication (JWT/OAuth), precise authorization, input validation, rate limiting, and continuous monitoring. No single technique is sufficient—you need defense in depth. By following these practices and maintaining awareness of evolving threats, you can build APIs that users trust and that withstand attacks. Security isn't a feature; it's a fundamental responsibility.