Appearance
Are you an LLM? You can read better optimized documentation at /advanced_topics/security.md for this page in Markdown format
Security best practices
This document outlines critical security considerations and best practices for maintaining and developing TheExampleApp. Given the application's architecture—a server-side rendered .NET Core application sourcing content from a headless CMS—security measures must address credential management, transport layer security, content injection vulnerabilities, and dependency management.
Credential management
Properly managing secrets, such as API keys and connection strings, is fundamental to application security. Hardcoding secrets in source code or configuration files poses a significant risk, as they can be accidentally exposed in public repositories.
Never Commit Secrets to Source Control
The appsettings.json file contains hardcoded API keys for Contentful.
File: TheExampleApp/appsettings.json
json
{
"ContentfulOptions": {
"DeliveryApiKey": "df2a18b8a5b4426741408fc95fa4331c7388d502318c44a5b22b167c3c1b1d03",
"PreviewApiKey": "10145c6d864960fdca694014ae5e7bdaa7de514a1b5d7fd8bd24027f90c49bbc",
"SpaceId": "qz0n5cdakyl9",
"UsePreviewApi": false
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Note: The keys shown above are public, read-only credentials for Contentful's example space and are safe for demonstration purposes. However, if you are deploying your own production application, you must replace these with your own Contentful credentials and manage them securely through environment variables or a secret manager—never commit production API keys to source control.
Environment Variable Usage
When deploying your own production application with custom Contentful credentials, the recommended approach is to use environment variables. The ASP.NET Core configuration system is designed to read configuration values from multiple sources, with environment variables overriding values from appsettings.json by default.
The application is already configured to use this system, as seen in Startup.cs:
File: TheExampleApp/Startup.cs
csharp
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
CurrentEnvironment = env;
}
public IConfiguration Configuration { get; }
// ...
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
To override the Contentful configuration with your own credentials, set them as environment variables on the host machine or deployment environment (e.g., Heroku, Azure App Service). The variable names must match the configuration path.
| Configuration Key | Environment Variable Name |
|---|---|
DeliveryApiKey | ContentfulOptions__DeliveryApiKey |
PreviewApiKey | ContentfulOptions__PreviewApiKey |
SpaceId | ContentfulOptions__SpaceId |
UsePreviewApi | ContentfulOptions__UsePreviewApi |
For more details on how the configuration system works, refer to the Application Settings and Environment Variables documentation.
Azure Key Vault / Secret Managers
For environments with higher security requirements, consider using a dedicated secret manager like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault. These services provide centralized secret management, access policies, and auditing. ASP.NET Core has providers that integrate seamlessly with these services.
HTTPS enforcement
Enforcing HTTPS is essential to protect data in transit. The application includes a custom middleware rule to redirect HTTP traffic to HTTPS, which is crucial for production environments.
X-Forwarded-Proto Handling
The application is designed to run behind a reverse proxy or load balancer, which is a common pattern for containerized deployments. In such a setup, the proxy terminates the SSL connection and forwards the request to the application over HTTP.
The application correctly handles this by inspecting the X-Forwarded-Proto header, which is set by the proxy to indicate the original protocol used by the client.
File: TheExampleApp/Startup.cs
csharp
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var options = new RewriteOptions();
if (env.IsDevelopment())
{
// ...
}
else
{
// Custom rule to enforce HTTPS when behind a reverse proxy
options.Add((c) => {
var request = c.HttpContext.Request;
if(request.Headers.ContainsKey("X-Forwarded-Proto") && request.Headers["X-Forwarded-Proto"] == "http")
{
var response = c.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
c.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] = "https://" + request.Host + request.Path + request.QueryString;
}
} );
app.UseExceptionHandler("/Error");
}
// ...
app.UseRewriter(options);
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
This implementation ensures that if a request was originally made over HTTP, the user is issued a 301 Moved Permanently redirect to the https version of the same URL. This logic is correctly placed within the else block, so it only runs in non-development environments.
Staging Environment Configuration
The application includes special handling for staging environments. When the environment is identified as "Staging", the application can redirect Contentful API requests to an alternative host specified via the StagingHost environment variable.
File: TheExampleApp/Startup.cs
csharp
if (CurrentEnvironment.IsStaging())
{
var host = Environment.GetEnvironmentVariable("StagingHost");
if (!string.IsNullOrEmpty(host))
{
services.AddSingleton((ip) =>
{
var stagingHandler = new StagingMessageHandler
{
StagingHost = host
};
return new HttpClient(stagingHandler);
});
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Security Consideration: This mechanism allows the application to use a different Contentful instance in staging environments. Ensure that the StagingHost environment variable is properly secured and validated, as it controls where API requests are directed. Only use this feature in trusted staging environments.
Content security
Since the application's primary function is to render content from an external CMS, it is vital to protect against content-based injection attacks, particularly Cross-Site Scripting (XSS).
XSS Prevention and Markdown Sanitization
The application uses the Markdig library to convert Markdown from Contentful into HTML via a custom MarkdownTagHelper.
File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
csharp
using Markdig;
// ...
public class MarkdownTagHelper : TagHelper
{
// ...
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// ...
var content = await GetContent(output);
var markdown = content;
// The ToHtml method is called here.
var html = Markdown.ToHtml(markdown ?? "");
output.Content.SetHtmlContent(html ?? "");
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Potential Vulnerability: By default, Markdig does not sanitize the resulting HTML. It is designed for performance and assumes trusted content. If a content editor in Contentful includes malicious HTML or JavaScript within a Markdown field (e.g., <script>alert('XSS')</script>), it will be rendered directly into the page and executed by the user's browser.
Recommendation: To mitigate this risk, a custom Markdig pipeline that includes an HTML sanitizer must be configured.
- Add a Sanitizer Library: Add a NuGet package like
HtmlSanitizerto the project. - Configure a Secure Pipeline: Create a static, reusable
MarkdownPipelineinstance that uses the sanitizer.
Example (Conceptual):
csharp
// In a new utility class or within the TagHelper
private static readonly MarkdownPipeline _pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions() // Use desired Markdig extensions
.Build();
// In ProcessAsync method
var html = Markdown.ToHtml(markdown ?? "", _pipeline);
var sanitizedHtml = new HtmlSanitizer().Sanitize(html); // Sanitize the output
output.Content.SetHtmlContent(sanitizedHtml);1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
This ensures that any dangerous HTML tags or attributes are stripped before the content is rendered, effectively preventing XSS attacks originating from the CMS content.
Input Validation
The application demonstrates good input validation practices in its handling of the locale query string parameter. The CustomRequestCultureProvider validates the incoming locale against both the application's supported cultures and the list of available locales fetched from the Contentful space.
File: TheExampleApp/Startup.cs
csharp
// ...
new CustomRequestCultureProvider(async (s) => {
if (s.Request.Query.ContainsKey(LOCALE_KEY))
{
var locale = s.Request.Query[LOCALE_KEY];
var client = s.RequestServices?.GetService<IContentfulClient>();
var contentfulLocales = new List<Locale>();
if(client != null)
{
var space = await client.GetSpace();
contentfulLocales = space.Locales;
}
// 1. Validate against locales available in Contentful
if(contentfulLocales.Any(c => c.Code == locale) == false)
{
// Not a supported locale in Contentful, return the default.
return null;
}
// 2. Validate against locales supported by the application's static resources
if(SupportedCultures.Any(c => string.Equals(c.ToString(), locale, StringComparison.OrdinalIgnoreCase)) == false)
{
// ... fallback logic ...
}
// ...
}
return null;
}),
// ...1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
This multi-layered validation prevents unexpected behavior or potential injection attacks through the locale parameter by ensuring it conforms to a known-good set of values.
Session security
The application uses ASP.NET Core session state to store user-specific data, such as the selected locale and visited lessons.
File: TheExampleApp/Startup.cs
csharp
services.AddSession(options => {
// IdleTimeout is set to a high value to confirm to requirements for this particular application.
// In your application you should use an IdleTimeout that suits your application needs or stick to the default of 20 minutes.
options.IdleTimeout = TimeSpan.FromDays(2);
});1
2
3
4
5
2
3
4
5
Security Consideration: The session idle timeout is configured to 2 days, which is significantly longer than the default 20 minutes. While this may be necessary for the application's specific requirements, extended session timeouts increase the window of opportunity for session hijacking attacks.
Recommendations:
- Ensure that session cookies are configured with the
SecureandHttpOnlyflags to prevent transmission over unencrypted connections and access via JavaScript. - Consider implementing additional session validation mechanisms, such as binding sessions to IP addresses or user agents, if the extended timeout is required.
- Review whether the 2-day timeout is truly necessary, or if a shorter duration would be acceptable for most use cases.
Dependency security
Using outdated libraries and frameworks is one of the most significant security risks an application can face. The TheExampleApp project is built on several end-of-life components.
Keeping Packages Updated
The project's dependencies are severely outdated, posing a major security risk.
| Technology | Version | Status |
|---|---|---|
| .NET Core | netcoreapp2.1 | End-of-Life (August 2021) |
| ASP.NET Core | 2.1.5 | End-of-Life (August 2021) |
| Markdig | 0.15.4 | Very old version; latest is >0.30.0 |
Immediate Action Required: The highest priority for the development team should be to plan and execute an upgrade of the entire application to a supported .NET Long-Term Support (LTS) release (e.g., .NET 6 or .NET 8). This will provide critical security patches, performance improvements, and access to modern language features.
All other NuGet packages should also be updated to their latest stable versions after the framework upgrade.
Container Image Security
The application's Dockerfile uses deprecated Docker images that are no longer maintained by Microsoft.
File: Dockerfile
dockerfile
FROM microsoft/dotnet:2.1-sdk AS builder
# ...
FROM microsoft/aspnetcore:2.1
2
3
2
3
Issues Identified:
- Deprecated Image Repository: The
microsoft/dotnetandmicrosoft/aspnetcoreimages are obsolete. Microsoft migrated to themcr.microsoft.com/dotnetrepository in 2019. - Incomplete Version Tag: The runtime image tag appears truncated (
microsoft/aspnetcore:2.instead ofmicrosoft/aspnetcore:2.1), which could lead to unpredictable image selection. - No Security Updates: These deprecated images no longer receive security patches, leaving the containerized application vulnerable to known exploits.
Recommended Updates: When upgrading to a modern .NET version, update the Dockerfile to use current official images:
dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app/
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
ENV ASPNETCORE_ENVIRONMENT=Production
COPY --from=builder /app .
ENTRYPOINT ["dotnet", "TheExampleApp.dll"]1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Additional container security hardening recommendations:
- Run the container as a non-root user
- Use specific version tags rather than floating tags (e.g.,
8.0.1instead of8.0) - Regularly rebuild images to incorporate base image security updates
- Scan container images for vulnerabilities using tools like Docker Scout or Trivy
Security Scanning and Vulnerability Monitoring
To maintain a secure posture going forward, the team must adopt a proactive approach to dependency management.
- Automated Scanning: Integrate automated security scanning into the CI/CD pipeline. Tools like
dotnet list package --vulnerable, GitHub Dependabot, Snyk, or other commercial scanners can automatically detect dependencies with known vulnerabilities. - Container Image Scanning: Use container security scanning tools to identify vulnerabilities in Docker base images and dependencies.
- Regular Audits: Schedule regular reviews of all project dependencies to ensure they are up-to-date and actively maintained.
- Monitor Advisories: Subscribe to security advisories for the core technologies used in the stack (.NET, ASP.NET Core, Contentful SDK, etc.).