How to handle file downloads using JWT authentication?

I am writing webapp in Angular where authentication is performed using a JWT token, which means that each request has an “Authentication” header with all the necessary information.

This works well for REST calls, but I don’t understand how I should handle links to upload files hosted on the backend (the files are on the same server as the web services).

I can’t use regular links <a href='...'/> , since they will not have any header and authentication will fail. The same goes for the various window.open(...) spells.

Some solutions I was thinking about:

  • Create a temporary insecure download link on the server
  • Pass authentication information as url parameter and handle this case manually
  • Get data through XHR and save the client part of the file.

All of the above is less satisfactory.

1 is the solution I'm using right now. I don’t like this for two reasons: firstly, it’s not perfect security, and secondly, it works, but it requires a lot of work, especially on the server: load something that I need, call the service that generates a new "random" url, somewhere stores it (possibly in the database) and returns it to the client. The client receives the URL and uses window.open or similar. When requested, the new URL should check if it is valid and then return the data.

2 seems at least the same job.

3 seems like a lot of work, even with the use of accessible libraries and many potential problems. (I will need to provide my own boot status bar, load the entire file into memory, and then ask the user to save the file locally).

The task seems pretty simple, so I wonder if there is something much simpler that I can use.

I'm not necessarily looking for an Angular Way solution. Normal Javascript will be fine.

+76
javascript angularjs jwt
Apr 04 '15 at 22:21
source share
4 answers

Here's a way to upload it to the client using the upload attribute , fetch API, and URL.createObjectURL . You have to get the file using your JWT, convert the payload to a blob, put the blob in objectURL, set the source of the anchor tag for that objectURL and click this objectURL in javascript.

 let anchor = document.createElement("a"); document.body.appendChild(anchor); let file = 'https://www.example.com/some-file.pdf'; let headers = new Headers(); headers.append('Authorization', 'Bearer MY-TOKEN'); fetch(file, { headers }) .then(response => response.blob()) .then(blobby => { let objectUrl = window.URL.createObjectURL(blobby); anchor.href = objectUrl; anchor.download = 'some-file.pdf'; anchor.click(); window.URL.revokeObjectURL(objectUrl); }); 

The value of the download attribute will be the final file name. Optionally, you can extract the intended file name from the response header for the content location, as described in other answers .

+31
Mar 31 '17 at 5:34 on
source share

Technics

Based on this advice from Matthias Voloski of Auth0, a famous JWT evangelist, I solved it by creating a signed request with Hawk .

Quote from Woloski:

How do you solve this by creating, for example, a signed request, such as AWS.

Here you have an example of this method, which is used to activate links.

backend

I created an API to sign my download URLs:

Inquiry:

 POST /api/sign Content-Type: application/json Authorization: Bearer... {"url": "https://path.to/protected.file"} 

Answer:

 {"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"} 

With a signed URL we can get the file

Inquiry:

 GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c 

Answer:

 Content-Type: multipart/mixed; charset="UTF-8" Content-Disposition': attachment; filename=protected.file {BLOB} 

frontend ( jojoyuji )

This way you can do it all with the click of a button:

 function clickedOnDownloadButton() { postToSignWithAuthorizationHeader({ url: 'https://path.to/protected.file' }).then(function(signed) { window.location = signed.url; }); } 
+24
Feb 17 '16 at 0:11
source share

I would generate tokens for download.

Inside angular, make an authenticated request to get a temporary token (say, an hour), and then add it to the URL as a get parameter. Thus, you can download files in any way (window.open ...)

+4
Apr 19 '15 at 7:31
source share

Additional solution: use basic authentication. Although this requires a bit of work with the backend, tokens will not be visible in the logs, and URLs do not need to be signed.




Client side

An example URL could be:

http://jwt:<user jwt token>@some.url/file/35/download

Example with a dummy token:

http://jwt:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6IiIsImlhdCI6MH0.KsKmQOZM-jcy4l_7NFsv1lWfpH8ofniVCv75ZRQrWno@some.url/file/35/download

You can then add this to <a href="..."> or window.open("...") - the rest is handled by the browser.




Server side

The implementation here is up to you and depends on your server settings - it is not too different from using the ?token= query parameter.

Using Laravel, I went the easy way and converted the basic authentication password to the JWT Authorization: Bearer <...> header, allowing the middleware authentication process to handle everything else:

 class CarryBasic { /** * @param Request $request * @param \Closure $next * @return mixed */ public function handle($request, \Closure $next) { // if no basic auth is passed, // or the user is not "jwt", // send a 401 and trigger the basic auth dialog if ($request->getUser() !== 'jwt') { return $this->failedBasicResponse(); } // if there _is_ basic auth passed, // and the user is JWT, // shove the password into the "Authorization: Bearer <...>" // header and let the other middleware // handle it. $request->headers->set( 'Authorization', 'Bearer ' . $request->getPassword() ); return $next($request); } /** * Get the response for basic authentication. * * @return void * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException */ protected function failedBasicResponse() { throw new UnauthorizedHttpException('Basic', 'Invalid credentials.'); } } 
+2
Jul 30 '18 at 21:11
source share



All Articles