Tech Corner

IMPROVING APPLICATION SECURITY IN AN ASP.NET CORE API USING HTTP HEADERS

9 December, 2021
image

This article indicates a way to enhance the security of an ASP.NET Core Web API application by using including safety headers to all HTTP API responses. The protection headers are brought the usage of the NetEscapades.AspNetCore.SecurityHeaders Nuget bundle from Andrew Lock.

The headers are used to defend the session, no longer for authorization. The application uses Microsoft.Identity.Web to authorize the API requests. The protection headers are used to shield the consultation. Swagger is used in the improvement and the CSP desires to be weakened to permit swagger to paintings all through development. A strict CSP definition is used for the deployed surroundings.

Code:: GitHub – damienbod/AzureAD-Auth-MyUI-with-MyAPI: Azure AD Auth with ASP.NET CORE UI and ASP.ENT Core API

The NetEscapades.AspNetCore.SecurityHeaders Nuget package is added to the csproj file of the web applications. The Swagger Open API packages are added as well as the Microsoft.Identity.Web to protect the API using OAuth.

<ItemGroup>
							<PackageReference
								Include="Microsoft.Identity.Web" Version="1.15.2" />
							<PackageReference
								Include="IdentityModel.AspNetCore" Version="3.0.0" />
							<PackageReference
								Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.16.0" />
							<PackageReference
								Include="Swashbuckle.AspNetCore" Version="6.1.4" />
							<PackageReference
								Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.4" />
						</ItemGroup>
						

The security header definitions are added using the HeaderPolicyCollection class. I added this to a separate class to keep the Startup class small where the middleware is added. I passed a boolean parameter into the method which is used to add or remove the HSTS header and create a CSP policy depending on the environment.

public static HeaderPolicyCollection GetHeaderPolicyCollection(bool isDev)
						{
							var policy = new HeaderPolicyCollection()
								.AddFrameOptionsDeny()
								.AddXssProtectionBlock()
								.AddContentTypeOptionsNoSniff()
								.AddReferrerPolicyStrictOriginWhenCrossOrigin()
								.RemoveServerHeader()
								.AddCrossOriginOpenerPolicy(builder =>
								{
									builder.SameOrigin();
								})
								.AddCrossOriginEmbedderPolicy(builder =>
								{
									builder.RequireCorp();
								})
								.AddCrossOriginResourcePolicy(builder =>
								{
									builder.SameOrigin();
								})
								.RemoveServerHeader()
								.AddPermissionsPolicy(builder =>
								{
									builder.AddAccelerometer().None();
									builder.AddAutoplay().None();
									builder.AddCamera().None();
									builder.AddEncryptedMedia().None();
									builder.AddFullscreen().All();
									builder.AddGeolocation().None();
									builder.AddGyroscope().None();
									builder.AddMagnetometer().None();
									builder.AddMicrophone().None();
									builder.AddMidi().None();
									builder.AddPayment().None();
									builder.AddPictureInPicture().None();
									builder.AddSyncXHR().None();
									builder.AddUsb().None();
								});
						 
							AddCspHstsDefinitions(isDev, policy);
						 
							return policy;
						}

The AddCspHstsDefinitions defines different policies using the parameter. In development, the HSTS header is not added to the headers and a weak CSP is used so that the Swagger UI will work. This UI uses unsafe-inline Javascript and needs to be allowed in development. I remove swagger from all non-dev deployments due to this and force a strong CSP definition then.

private static void AddCspHstsDefinitions(bool isDev, HeaderPolicyCollection policy)
						{
							if (!isDev)
							{
								policy.AddContentSecurityPolicy(builder =>
								{
									builder.AddObjectSrc().None();
									builder.AddBlockAllMixedContent();
									builder.AddImgSrc().None();
									builder.AddFormAction().None();
									builder.AddFontSrc().None();
									builder.AddStyleSrc().None();
									builder.AddScriptSrc().None();
									builder.AddBaseUri().Self();
									builder.AddFrameAncestors().None();
									builder.AddCustomDirective("require-trusted-types-for", "'script'");
								});
								// maxage = one year in seconds
								policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains
								  (maxAgeInSeconds: 60 * 60 * 24 * 365);
							}
							else
							{
								// allow swagger UI for dev
								policy.AddContentSecurityPolicy(builder =>
								{
									builder.AddObjectSrc().None();
									builder.AddBlockAllMixedContent();
									builder.AddImgSrc().Self().From("data:");
									builder.AddFormAction().Self();
									builder.AddFontSrc().Self();
									builder.AddStyleSrc().Self().UnsafeInline();
									builder.AddScriptSrc().Self().UnsafeInline(); //.WithNonce();
									builder.AddBaseUri().Self();
									builder.AddFrameAncestors().None();
								});
							}
						}

In the Startup class, the UseSecurityHeaders method is used to apply the HTTP headers policy and add the middleware to the application. The env.IsDevelopment() is used to add or not to add the HSTS header. The default HSTS middleware from the ASP.NET Core templates was removed from the Configure method as this is not required. The UseSecurityHeaders is added before the swagger middleware so that the security headers are deployment to all environments.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
						{
							app.UseSecurityHeaders(
								SecurityHeadersDefinitions.GetHeaderPolicyCollection(env.IsDevelopment()));
						 
							if (env.IsDevelopment())
							{
								app.UseDeveloperExceptionPage();
						 
								app.UseSwagger();
								app.UseSwaggerUI(c =>
								{
									c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
								});
							}

The server header can be removed in the program class if using Kestrel. If using IIS, you probably need to use the web.config to remove this.

public static IHostBuilder CreateHostBuilder(string[] args) =>
									Host.CreateDefaultBuilder(args)
										.ConfigureWebHostDefaults(webBuilder =>
										{
											webBuilder
												.ConfigureKestrel(options => options.AddServerHeader = false)
												.UseStartup<Startup>();
										});

Running the application using a non-development environment, the securtiyheaders.com check returns good results. Everything is closed as this is an API with no UI.

If a Swagger UI is required, the API application can be run in the development environment. This could also be deployed if required, but in a production deployment, you probably don’t need this.

To support the swagger UI, a weakened CSP is used and the https://csp-evaluator.withgoogle.com/ check returns a more negative result.