Secure File Downloads

Sometimes a user needs to be able to access protected and secure files for download to their local browser. The challenge is that a browser must be able to access an anonymously accessible endpoint in order to download a file from an API via a URL. In order to do this, the API supports the use of a record stored in the DownloadToken table of our database. The user would obtain the token via an API endpoint that is secured by their user credentials and role-level access.

Here is the DownloadToken.cs file that maps back to the DownloadToken database table. The properties are documented in the code:

using System.ComponentModel.DataAnnotations.Schema;

namespace LymeTemplateApi.Models;

/// <summary>
/// The DownloadToken entity is intended to help facilitate
/// secure file downloads. In order to easily provide a user with a 
/// secure DownloadToken, you can use the static method 
/// FileDownloads.CreateDownloadToken to allow your API Controllers
/// to create a token by a secured user and return that token for
/// use with the DownloadFile API controller.
/// </summary>
[Table("DownloadToken")]
public class DownloadToken
{
    /// <summary>
    /// Primary key
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// The physical file path of the file to be downloaded.
    /// </summary>
    public string? FilePath { get; set; }

    /// <summary>
    /// The optional overriding filename that should be delivered to the user.
    /// </summary>
    public string? FileName { get; set; }

    /// <summary>
    /// The GUID Token created for the file download.
    /// </summary>
    public Guid Token { get; set; }

    /// <summary>
    /// The date the token was created.
    /// </summary>
    public DateTime DateEntered { get; set; }

    /// <summary>
    /// The date that the token expires.
    /// </summary>
    public DateTime ExpirationDate { get; set; }

    /// <summary>
    /// The date that the token was redeemed.
    /// </summary>
    public DateTime? Redeemed { get; set; }
}

In order for a user to redeem a token to download a file, the user can visit an API endpoint that resembles the following:

https://www.lymetemplate.com/api/DownloadFile?token=INSERT_TOKEN_GUID_VALUE_HERE

Here's a sample API endpoint that uses this pattern to secure a file:

/// <summary>
/// This endpoint is secured by default using Microsoft Entra ID.
/// </summary>
/// <returns>A GUID token value as a string.</returns>
[Route("SampleFileDownload")]
[HttpGet]
public ActionResult<string> SampleFileDownload()
{
    var token = FileDownloads.CreateDownloadToken(db, @"C:\Temp\SuperSecretGuide.pdf");
    return Ok(token.Token);
}

In the front end app, you can use the DownloadSecureFileService Angular service to help facilitate the usage of this pattern.

import { DownloadSecureFileService } from './download-secure-file.service';

...

constructor(private downloadSecureFile: DownloadSecureFileService) {}

...

downloadSecuredFile() {
    this.downloadSecureFile('MySecuredEndpoint/SampleFileDownload').subscribe();
}