When building modern ASP.NET Core applications, it’s common to serve files such as documents, PDFs, images, or reports to authenticated users. However, if files are stored in a public wwwroot folder or exposed directly through static file middleware, they can be accessed by anyone who knows the URL, even without proper authorization.
This can lead to data leaks, information disclosure, and compliance issues. To prevent this, we must implement a secure file-serving mechanism with authorization checks.
Why Not Serve Sensitive Files From?
The wwwroot folder is meant for public static assets (like CSS, JS, and images). Anything inside it is directly accessible via URL.
For sensitive files (e.g., invoices, medical records, private reports):
Place them outside
wwwroot.Serve them via controller endpoints with authentication & authorization.
Step 1: Project Setup
Create a new project (if not already):
dotnet new webapp -n SecureFileDemo
Enable authentication (if required) or use custom policies/roles.
Step 2: Store Files Outside wwwroot
Suppose we store private files inside SecureFiles at the root of the project:
/SecureFiles
/Reports
report1.pdf
report2.pdf
These files are not directly accessible via the browser.
Step 3: Create a Secure File Controller
We’ll build an endpoint that:
Validates the user identity.
Checks authorization (roles, claims, policies).
Serves the file securely.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading.Tasks;
namespace SecureFileDemo.Controllers
{
[Authorize] // ensures only logged-in users
public class FilesController : Controller
{
private readonly string _fileStoragePath = Path.Combine(Directory.GetCurrentDirectory(), "SecureFiles");
[Authorize(Roles = "Admin,Manager")] // Example: restrict further
[HttpGet("files/{fileName}")]
public async Task<IActionResult> GetFile(string fileName)
{
// Prevent path traversal attack
var safeFileName = Path.GetFileName(fileName);
var filePath = Path.Combine(_fileStoragePath, safeFileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound("File not found.");
}
var memory = new MemoryStream();
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var contentType = "application/octet-stream"; // default
return File(memory, contentType, safeFileName);
}
}
}
Join the conversation! Your thoughts help the community grow.