AngularJS + Web API - How to securely download excel file without having to re-login

I have an application built using AngularJS and the .NET Web API that uses Http Basic Authentication for security. They are located on different subdomains, for example: mydomain.com and api.mydomain.com

I have a secure url on the api web site https://api.mydomain.com/downloadFile 'that generates an excel file that I need to upload on the client. I tried to get the file to be downloaded via angularjs using HTML5 Blob objects, but continued to receive "File damaged" error messages when the file tried to open.

The solution I came across was to create a hidden iframe to load the file

<iframe style="display:none;" ng-src="{{downloadFileUrl}}"></iframe>

And setting $ scope.downloadFileUrl in the controller

$scope.downloadFile = function () {
    $scope.downloadFileUrl = $sce.trustAsResourceUrl('https://api.mydomain.com/downloadFile');
};

This works, but I have to log into the api again because the HTTP “Authorization” header is not sent as part of the request from the iframe. Is there a way to pass the “Authorization” header with an iframe request, or in some other way to safely download the file from the server without having to re-enter?

+4
source share
2 answers

I also use the iframe loading method. This is perhaps the most reliable download method supported by the cross browser. Therefore, I agree with your method here.

, , . (), , .

, . : , , , ( ). - ( db ). 10 . , URL- . , .

: https://api.mydomain.com/downloadFile/ {fileId}? ticket = fk37cltps7

+1

( ) , , , , -

internal const string DownloadPurpose ="FileDownload";

[Route("GenerateDownloadToken")]
public async Task<IHttpActionResult> GenerateDownloadToken()
{
    var userId = Guid.Parse(User.Identity.GetUserId());
    var t = await UserManager.GenerateUserTokenAsync(DownloadPurpose, userId);

    return Ok(t);
}

private async Task<bool> VerifyUserTokenAsync(string token, Guid userId)
{
    var returnVar = await UserManager.VerifyUserTokenAsync(userId, AccountController.DownloadPurpose, token, TimeSpan.FromMinutes(1));
    if (returnVar && !User.Identity.IsAuthenticated)
    {
        //for using the logic to restrict access within our Dto layer
        var appUser = await UserManager.FindByIdAsync(userId);
        //hopefully changing this will not cause problems downstream - we do not want cookies going back and forward
        User = new RequestOnlyPrincipal(appUser.UserName, await UserManager.GetRolesAsync(userId));
    }
    return returnVar;
}

public static class TimeSpanTokenExtensions
{
    //a bit of a hack really
    public static async Task<bool> VerifyUserTokenAsync<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string purpose, string token, TimeSpan tokenLifeSpan) 
        where TUser : class, IUser<TKey>
        where TKey : IEquatable<TKey>
    {
        var provider = (DataProtectorTokenProvider<TUser, TKey>)manager.UserTokenProvider;
        TimeSpan defaultSpan = provider.TokenLifespan;
        provider.TokenLifespan = tokenLifeSpan;
        var returnVar = await manager.VerifyUserTokenAsync(userId, purpose, token);
        provider.TokenLifespan = defaultSpan;
        return returnVar;
    }
}

private List<Stream> _streamsToDispose = new List<Stream>();

[Route("ScenarioResources")]
[HttpGet]
public async Task<HttpResponseMessage> GetResourcesForScenario([FromUri]DowloadFileSetModel model)
{
    if (!ModelState.IsValid)
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
    }

    if (!await VerifyUserTokenAsync(model.Token, model.UserId))
    {
        return Request.CreateResponse(HttpStatusCode.Unauthorized);
    }

    //logic here to test the specific user has the required access rights
    var path = //logic to obtain path here, using properties of model
    FileStream stream = new FileStream(path, FileMode.Open);
    _streamsToDispose.Add(stream); 
    var result = new HttpResponseMessage()
    {
        Content = new StreamContent(stream)
    };
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = "easy to read.xls"//suggested filename
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        _streamsToDispose.ForEach(s=>s.Dispose());
    }
    base.Dispose(disposing);
}

public class DowloadFileSetModel
{
    [Required]
    public Guid EntitySetId { get; set; }
    [Required]
    public Guid UserId { get; set; }
    [Required]
    public string Token { get; set; }
}

JS

function downloadFileLink(actionName, entitySetId) {
    return $http({
        method: 'POST', //specify post so as not to cache etc
        url: 'api/Account/GenerateDownloadToken'
    }).then(function (response) {
        var params = {
            EntitySetId: entitySetId,
            Token: response.data,
            UserId: tokenStorageService.getUserId()
        };
        var location = window.location.origin + '/api/utilities/' + actionName 
            + '?' + $httpParamSerializerJQLike(params);
        return $sce.trustAsResourceUrl(location);
    }, log.error);
}
0

All Articles