20 API Security Best Practices
A comprehensive checklist for securing your APIs against common vulnerabilities and attacks.
Authentication & Authorization
1. Strong Authentication
Use OAuth 2.0 or JWT for authorized access.
// ASP.NET Core JWT Configuration
services.AddAuthentication(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"]))
};
});
2. Access Control
Define granular permissions for endpoints.
[Authorize(Policy = "AdminOnly")]
[HttpDelete("users/{id}")]
public async Task<IActionResult> DeleteUser(int id)
{
// Only admins can delete users
}
// Policy definition
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("CanReadOrders", policy =>
policy.RequireClaim("permission", "orders:read"));
});
Transport Security
3. HTTPS Encryption
Transmit data securely with HTTPS.
// Force HTTPS in ASP.NET Core
public void Configure(IApplicationBuilder app)
{
app.UseHttpsRedirection();
app.UseHsts();
}
// Program.cs
builder.Services.AddHttpsRedirection(options =>
{
options.HttpsPort = 443;
});
4. Data Encryption
Encrypt sensitive data in transit and at rest.
// Encrypt sensitive data
public class EncryptionService
{
public string Encrypt(string plainText)
{
using var aes = Aes.Create();
var encryptor = aes.CreateEncryptor(key, iv);
// ... encryption logic
}
public string Decrypt(string cipherText)
{
using var aes = Aes.Create();
var decryptor = aes.CreateDecryptor(key, iv);
// ... decryption logic
}
}
Input Validation
5. Sanitize Input
Sanitize all incoming data.
public class CreateUserRequest
{
[Required]
[StringLength(100, MinimumLength = 2)]
[RegularExpression(@"^[a-zA-Z\s]+$")]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
}
// Custom sanitization
public static string SanitizeInput(string input)
{
if (string.IsNullOrEmpty(input)) return input;
return HttpUtility.HtmlEncode(input.Trim());
}
6. Secure Data Validation
Validate input and output data.
[HttpPost]
public IActionResult CreateOrder([FromBody] OrderRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Additional business validation
if (request.Quantity <= 0 || request.Quantity > 1000)
{
return BadRequest("Invalid quantity");
}
// Process order...
}
Error Handling
7. Secure Error Messages
Avoid revealing sensitive information in errors.
// Bad - reveals internal details
catch (SqlException ex)
{
return StatusCode(500, ex.Message); // Exposes SQL errors!
}
// Good - generic error message
catch (Exception ex)
{
_logger.LogError(ex, "Error processing request");
return StatusCode(500, new { message = "An error occurred" });
}
8. Disable Default Errors
Prevent revealing internal details.
// Configure exception handling
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new
{
error = "An unexpected error occurred",
requestId = Activity.Current?.Id ?? context.TraceIdentifier
});
});
});
Rate Limiting & Throttling
9. Rate Limiting
Prevent API abuse with rate limiting.
// ASP.NET Core 7+ Rate Limiting
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", opt =>
{
opt.Window = TimeSpan.FromMinutes(1);
opt.PermitLimit = 100;
opt.QueueLimit = 10;
});
options.AddSlidingWindowLimiter("sensitive", opt =>
{
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 6;
opt.PermitLimit = 10;
});
});
[EnableRateLimiting("api")]
[HttpGet("products")]
public IActionResult GetProducts() { }
10. Throttle Login Attempts
Prevent brute-force attacks.
public class LoginAttemptService
{
private readonly IMemoryCache _cache;
public bool IsBlocked(string ipAddress)
{
var key = $"login_attempts:{ipAddress}";
var attempts = _cache.GetOrCreate(key, entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(15);
return 0;
});
return attempts >= 5;
}
public void RecordFailedAttempt(string ipAddress)
{
var key = $"login_attempts:{ipAddress}";
_cache.Set(key, _cache.Get<int>(key) + 1,
DateTimeOffset.Now.AddMinutes(15));
}
}
Logging & Monitoring
11. Logging and Auditing
Maintain comprehensive logs.
public class AuditMiddleware
{
public async Task InvokeAsync(HttpContext context)
{
var auditLog = new AuditLog
{
Timestamp = DateTime.UtcNow,
UserId = context.User?.Identity?.Name,
IpAddress = context.Connection.RemoteIpAddress?.ToString(),
Method = context.Request.Method,
Path = context.Request.Path,
UserAgent = context.Request.Headers.UserAgent
};
_logger.LogInformation("API Request: {@AuditLog}", auditLog);
await _next(context);
}
}
12. Security Testing
Regularly assess for vulnerabilities.
// Integration tests for security
[Fact]
public async Task UnauthorizedAccess_Returns401()
{
var response = await _client.GetAsync("/api/admin/users");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task SqlInjection_IsPrevented()
{
var maliciousInput = "'; DROP TABLE Users; --";
var response = await _client.GetAsync($"/api/users?search={maliciousInput}");
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
Token Management
13. Token Expiration
Set short-lived access tokens.
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(15), // Short-lived
SigningCredentials = credentials
};
// Use refresh tokens for longer sessions
public class RefreshTokenService
{
public string GenerateRefreshToken()
{
var randomBytes = new byte[64];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
return Convert.ToBase64String(randomBytes);
}
}
14. Use CSRF Tokens
Prevent unauthorized requests.
// Add anti-forgery token
services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
options.Cookie.Name = "CSRF-TOKEN";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
// Validate in controller
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult ProcessPayment(PaymentRequest request) { }
Security Headers
15. Security Headers
Use CSP and X-XSS-Protection.
// Add security headers middleware
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; script-src 'self'");
await next();
});
16. CORS Configuration
Restrict cross-origin requests.
services.AddCors(options =>
{
options.AddPolicy("Production", builder =>
{
builder
.WithOrigins("https://myapp.com", "https://admin.myapp.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Authorization", "Content-Type")
.SetPreflightMaxAge(TimeSpan.FromMinutes(10));
});
});
API Design
17. API Versioning
Gracefully handle changes and backward compatibility.
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version")
);
});
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase { }
18. Safe API Documentation
Avoid revealing sensitive information.
// Swagger configuration - hide sensitive endpoints
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
// Don't expose internal endpoints
c.DocInclusionPredicate((docName, apiDesc) =>
{
return !apiDesc.RelativePath.Contains("internal");
});
});
Session Management
19. Secure Session Management
Invalidate sessions securely.
public async Task Logout()
{
// Invalidate refresh token
await _tokenService.RevokeRefreshToken(userId);
// Clear session
HttpContext.Session.Clear();
// Sign out
await HttpContext.SignOutAsync();
}
// Token revocation
public class TokenRevocationService
{
private readonly IDistributedCache _cache;
public async Task RevokeToken(string tokenId, TimeSpan expiration)
{
await _cache.SetStringAsync(
$"revoked:{tokenId}",
"revoked",
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiration }
);
}
}
20. Regular Updates
Keep API up-to-date with patches.
<!-- Enable automatic NuGet updates in CI/CD -->
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
<!-- Use Dependabot or similar for dependency updates -->
Security Checklist
| Category | Item | Priority |
|---|---|---|
| Auth | OAuth 2.0 / JWT | Critical |
| Auth | Role-based access control | Critical |
| Transport | HTTPS everywhere | Critical |
| Input | Input validation | Critical |
| Input | SQL injection prevention | Critical |
| Errors | Generic error messages | High |
| Rate Limiting | API throttling | High |
| Logging | Audit logging | High |
| Headers | Security headers | Medium |
| Testing | Regular security audits | Medium |
Sources
APIs/API security.jpg