admin管理员组

文章数量:1323014

In my angularjs application I am municating with a backend server that requires basic access authentication via http header. I have implemented the authentication mechanism on the client side as described here.

angular.module('myAuthModule')
.config(['$httpProvider', '$stateProvider',
    function ($httpProvider, $stateProvider) {
        $httpProvider.interceptors.push('securityInterceptor');
    }])
.factory('securityInterceptor', ['$location', '$window', '$q',
    function ($location, $window, $q) {
        return {
            request: function (config) {
                config.headers = config.headers || {};
                if ($window.sessionStorage.token) {
                    config.headers['Auth-Key'] = $window.sessionStorage.token;
                }
                return config;
            },
            response: function (response) {
                if (response.status === 401 || response.status === 403) {
                    $location.path('/login');
                }
                return response || $q.when(response);
            }
        };
    }
]);

So far so good, handling xhr requests within the angular app works as expected.

The problem is that I need to provide a download link for pdf documents. My backend server has a /Document/Pdf/:id resource that serves a application/pdf response with ContentDisposition: attachment which also requires authentication. I understand that I cannot initiate a download using xhr, however both providing a link to the document download via ngHref and calling a function that does for example $window.open('/Document/Pdf/13') lead to a 401 Unauthorized response by the server.

What am I missing here?

In my angularjs application I am municating with a backend server that requires basic access authentication via http header. I have implemented the authentication mechanism on the client side as described here.

angular.module('myAuthModule')
.config(['$httpProvider', '$stateProvider',
    function ($httpProvider, $stateProvider) {
        $httpProvider.interceptors.push('securityInterceptor');
    }])
.factory('securityInterceptor', ['$location', '$window', '$q',
    function ($location, $window, $q) {
        return {
            request: function (config) {
                config.headers = config.headers || {};
                if ($window.sessionStorage.token) {
                    config.headers['Auth-Key'] = $window.sessionStorage.token;
                }
                return config;
            },
            response: function (response) {
                if (response.status === 401 || response.status === 403) {
                    $location.path('/login');
                }
                return response || $q.when(response);
            }
        };
    }
]);

So far so good, handling xhr requests within the angular app works as expected.

The problem is that I need to provide a download link for pdf documents. My backend server has a /Document/Pdf/:id resource that serves a application/pdf response with ContentDisposition: attachment which also requires authentication. I understand that I cannot initiate a download using xhr, however both providing a link to the document download via ngHref and calling a function that does for example $window.open('/Document/Pdf/13') lead to a 401 Unauthorized response by the server.

What am I missing here?

Share Improve this question edited Feb 10, 2014 at 7:47 jhnwsk asked Feb 7, 2014 at 16:28 jhnwskjhnwsk 95312 silver badges15 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 6

Having explored the possibilities given by @Geoff Genz with the addition of a fourth - data-uri option, which unfortunately does not allow defining filenames - I decided to go for a different approach.

I added a method to the API which generates a one-time download link based on a normally authenticated request and download it straight away. The angular handler bees very simple

.factory('fileFactory', ['$http', '$window',
    function ($http, $window) {
        return {
            downloadFile: function (fileId) {
                return $http(
                    {
                        method: "POST",
                        data: fileId,
                        url: '/api/Files/RequestDownloadLink',
                        cache: false
                    }).success(function (response) {
                        var url = '/api/File/' + response.downloadId;
                        $window.location = url;
                    });
            }
        };
    }]);

This is not perfect but I feel is least hack-ish. Also this works for me because I have full control of the front- and back-end.

There is not a simple solution to this. You've already discovered that you cannot download via Ajax, so you can't set a custom header that way. Nor can you set a custom header on a browser generated GET (like an href) or POST (like a form submit). I can suggest three different approaches, all of which will require some modifications on your server:

(1) Use Basic or Digest auth on your web page, so the browser will generate and send the Authorization header with those credentials.

(2) Set the token in "authorization" cookie that will be passed with the request and validate the token server side.

(3) Finally, the way we've implemented this is to use a POST request instead of a GET for the download. We POST to a hidden IFrame on the same page and have the server set the appropriate Content-Disposition header such as "attachment; filename="blah.pdf"" on the response. We then send the authorization token as a hidden field in the form.

None of these are ideal, and I know our solution feels kind of hacky, but I've not seen any more elegant approaches.

本文标签: