There are four common vulnerabilities in web applications. Be aware of these risks, master features of the technology stacks that help you secure your apps and prevent security breaches is necessary.
Cross-site scripting attacks (XSS). Core tip: All data received from clients are untrusted. When you want to output the content, keep an eye on any possibility of including any executable scripts.
https://en.wikipedia.org/wiki/Cross-site_scripting
https://owasp.org/www-community/attacks/xss/
https://docs.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-3.1SQL injection attacks. Core tip: The concatenation of raw SQL command text with parameters or parts from an untrusted source should be seriously validated.
https://en.wikipedia.org/wiki/SQL_injection
https://portswigger.net/web-security/sql-injection
https://docs.microsoft.com/en-us/ef/core/querying/raw-sqlCross-Site Request Forgery (CSRF), also known as one-click attack or session riding. Core tip: Two websites are browsed, one log in and another is malicious. Submit requests from the malicious website attached with your valid cookie authentication is the common way to attack.
https://en.wikipedia.org/wiki/Cross-site_request_forgery
https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-3.1Open redirect attacks, also known as "Unvalidated Redirects and Forwards". Core tip: If the login redirects with an unchecked query parameter, users from a fake link could be redirected to a similar login page and causing their credential data leaks.
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/understanding-and-discovering-open-redirect-vulnerabilities/
https://docs.microsoft.com/en-us/aspnet/core/security/preventing-open-redirects?view=aspnetcore-3.1
.Net core web API 3.1 is the latest framework of Microsoft to develop REST API.
Use cookie authentication without ASP.NET Core Identity (https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1.) is easy and quick. HttpOnly cookies will be used by default. Httponly flag is very important to avoid any XSS attack and has other benefits (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). Cookie solution relies on client's cookie support.
Another common authentication solution for API is to use JWT (JSON Web Token, https://jwt.io). https://jasonwatmore.com/post/2019/10/11/aspnet-core-3-jwt-authentication-tutorial-with-example-api. In start.cs:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
if (Settings.Cors?.Length > 0)
{
services.AddCors(options =>
{
options.AddPolicy("platform",
builder =>
{
builder.WithOrigins(Settings.Cors)
// Support https://*.domain.com
.SetIsOriginAllowedToAllowWildcardSubdomains()
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
// https://stackoverflow.com/questions/24687313/what-exactly-does-the-access-control-allow-credentials-header-do
// JWT is not a cookie solution, disable it without allow credential
// .AllowCredentials()
.DisallowCredentials()
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
// Without it will popup error: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response
.AllowAnyHeader()
// Web Verbs like GET, POST, default enabled
.AllowAnyMethod();
});
});
}
// Configue compression
// https://gunnarpeipman.com/aspnet-core-compress-gzip-brotli-content-encoding/
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
});
// Configue JWT authentication
// https://jwt.io/
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// Is SSL only
options.RequireHttpsMetadata = Settings.SSL;
// Save token, True means tokens are cached in the server for validation
options.SaveToken = false;
// Token validation parameters
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(app.Configuration.SymmetricKey)),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Enable HTTPS redirect
if (Settings.SSL)
app.UseHttpsRedirection();
app.UseRouting();
// Enable CORS (Cross-Origin Requests)
// The call to UseCors must be placed after UseRouting, but before UseAuthorization
if (Settings.Cors?.Length > 0)
{
app.UseCors("platform");
}
app.UseAuthentication();
app.UseAuthorization();
// Enable compression
app.UseResponseCompression();
app.UseEndpoints(endpoints =>
{
// Apply authentication by default
endpoints.MapControllers().RequireAuthorization();
});
}
After the user log in successfully, add the codes below to generate the token:
/// <summary>
/// Login for authentication
/// 登录授权
/// </summary>
/// <param name="model">Data model</param>
/// <returns>Result</returns>
[AllowAnonymous]
[HttpPost("Login")]
public async Task Login([FromBody]LoginModel model)
{
// Act
var result = await Service.LoginAsync(model);
if (result.OK)
{
// Logined user id
var userId = result.Data.Get("token_user_id", 0);
// User role
var role = result.Data.Get("role", UserRole.User);
// Token handler
var tokenHandler = new JwtSecurityTokenHandler();
// Key bytes
var key = Encoding.ASCII.GetBytes(App.Configuration.SymmetricKey);
// Token descriptor
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, userId.ToString()),
new Claim(ClaimTypes.Role, role.ToString().ToLower()),
}),
// Suggest to refresh it at 5 minutes interval, two times to update
Expires = DateTime.UtcNow.AddMinutes(12),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)
};
// Hold the token value and then return to client
var token = tokenHandler.CreateToken(tokenDescriptor);
result.Data["authorization"] = tokenHandler.WriteToken(token);
}
// Output
await ResultContentAsync(result);
}
Because the token is stateless, Web APIs are always facing a replay attack, also known as playback attack. bearer.SaveToken = true means you could access it through await HttpContext.GetTokenAsync("access_token") for any outgoing request. Add necessary validation logic in the database side is helpful. A short-lived, strict authentication with rate-limiting policy token solution will make the project much stronger.
Top comments (0)