Asp.Net Core Web API’s Security Best Practices
In the modern digital landscape, Web APIs are critical for enabling communication between different systems and services. However, they also present a lucrative target for attackers. Ensuring the security of your Web APIs is paramount. In this blog post, we’ll explore best practices for securing Web APIs in .NET C#.
We will cover every section in detail and will see it in action. So let’s get started.
1- Data Protection using HTTPS only:
Always use HTTPS to encrypt data in transit. Avoid using HTTP to prevent data from being intercepted. HTTPS is more securable and reliable protocol.
Program.cs
app.UseHttpsRedirection();
Copy
2- Authentication:
Always use a strong authentication mechanism to protect our API more securely, allowing only authenticated users. We can achieve robust authentication mechanisms such as OAuth, OpenID Connect, or JWT (JSON Web Tokens). Avoid basic authentication with plaintext passwords.
Perform the following steps.
AuthController.cs:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace BlogApiApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
List<UserLoginModel> UserList;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
UserList = new List<UserLoginModel>();
UserList.Add(new UserLoginModel { Username = "admin", Password = "admin123" });
UserList.Add(new UserLoginModel { Username = "Jhon", Password = "Jhon123"});
}
[HttpPost("login")]
public IActionResult Login([FromBody] UserLoginModel user) {
//Validate user credentials(this is just an example, replace with your logic)
var UserListRes = UserList.Where(x => x.Username ==
user.Username && user.Password == user.Password).FirstOrDefault();
if (UserListRes != null)
{
//2- JWT Token Auth.
var token = GenerateJwtToken(UserListRes.Username,
"Admin");
return Ok(new { Token = token });
}
return Unauthorized();
}
private string GenerateJwtToken(string username, string role) {
try
{
var jwtSettings =
_configuration.GetSection("JwtSettings");
var securityKey = new
SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"])); var credentials = new SigningCredentials(securityKey,
SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, role) // Assign Role
dynamically
};
var token = new JwtSecurityToken(
issuer: jwtSettings["Issuer"],
audience: jwtSettings["Audience"],
claims: claims,
expires:
DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings["ExpiryInMinutes"])), signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
catch (Exception ex)
{
throw ex;
}
}
}
}
public class UserLoginModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
Copy
Pragram.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.Reflection.Metadata;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
#region Configure JWT authentication
var jwtSettings = builder.Configuration.GetSection("JwtSettings"); builder.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 = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new
SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"])) };
});
#endregion
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Copy
Appsetting.json:
{
"AllowedHosts": "*",
"JwtSettings": {
"Issuer": "YourIssuer",
"Audience": "YourAudience",
"Key": "YourVeryStrongSecretKeyThatIsLongEnough123456"
}
}
Copy
Test Result:
Now, Let’s run the Web API project by pressing F5 and hitting the API through Postman. Finally, our API worked and returned the token.
3- Token Expiry and Rotation:
Ensure that tokens have a short lifespan and can be rotated regularly. Use refresh tokens for obtaining new access tokens. As you can see we have setup the token expiry is 15 mins.
Appsetting.json:
{
"AllowedHosts": "*",
"JwtSettings": {
"Issuer": "YourIssuer",
"Audience": "YourAudience",
"Key": "YourVeryStrongSecretKeyThatIsLongEnough123456",
"ExpiryInMinutes": 15 //Token Timeout.
}
}
Copy
Test Results: Token expired after 15 minutes. Now you have to generate a new token and then would be able to access again against that token.
4- Role-Based Access Control (RBAC)
Implement role-based access control to restrict access to API endpoints based on user roles. 4 (i). Every user must have role assigned.
d) Admin will be able to access the GetSecureData & WelcomeAdmin methods. e) User will be able to access the GetSecureData & WelcomeUser methods......
.
.
.
.
.