How can I download a file from frontend with Authorization Header
Asked Answered
M

1

8

Here is my backend code. It has 2 protected routes. Authorization Header is needed (Bearer token).

  • These routes allows you to download your photo and your cv.

I have no issue with my backend. It is working with postman and mobile applications. I think there is a limitation for frontend.

I do NOT want

  • I don't want to use fetch to download these files as blob. Because it doesn't allow to browser to show its progress bar.
  • I don't want to change my authentication method from Bearer token to cookie. I can already do this with cookie.

I wonder why Bearer token is more popular than cookie If I have to use cookie to make this happen.

Backend

// other imports .....
const path = require('path');
const fs = require('fs');
const express = require('express');
const app = express();

app.use((req, res, next) => {
  try {
    const token = req.get('Authorization');
    if(!token) {
      throw new Error('401');
    }
    const userId = getIdFromToken(token);
    if(!userId) {
      throw new Error('401');
    }
    res.locals.userId = userId;
    next();
  } catch (error) {
    next(error);
  }
})
app.get('/api/me/photo', (req, res) => {
  const userId = res.locals.userId;
  
  const imagePath = path.resolve(process.env.DISC_PATH, 'images', `${userId}.png`);
  res.attachment('me.png');
  res.set('Content-Type', 'image/png');
  const stream = fs.createReadStream(imagePath);
  res.pipe(stream);
})

app.get('/api/me/cv', (req, res) => {
  const userId = res.locals.userId;
  
  const pdfPath = path.resolve(process.env.DISC_PATH, 'cv', `${userId}.png`);
  res.attachment('my-cv.pdf');
  res.set('Content-Type', 'application/pdf');
  const stream = fs.createReadStream(pdfPath);
  res.pipe(stream);
})

...

// error handling, etc...

Frontend


<html>
<body>

<!-- How can I send the Authorization header here -->
<img src="/api/me/photo" />

<!-- How can I send the Authorization header here -->
<a href="/api/me/cv">Download My CV</a>

<!-- How can I send the Authorization header here -->
<button id="btn"> Download My CV (V2) </button>

<script>
  const btn = document.getElementById('btn');
  btn.addEventListener('click', () => {
    // How can I send the Authorization header here
    window.open('/api/me/cv');
  });
</script>

</body>
</html>

Makell answered 10/6, 2022 at 13:48 Comment(0)
S
4

I wonder why Bearer token is more popular than cookie If I have to use cookie to make this happen.

One advantage of an Authorization: Bearer header over cookie-based authentication is precisely that the browser does not automatically include the header in a request to the given URL. So you cannot be tricked into clicking on a link that would trigger an authenticated request that you did not intend.

In other words: With Authorization: Bearer as authentication mechanism you are safe from "cross-site request forgery" (CSRF) attacks.

Backends that use cookie-based authentication have to implement extra countermeasures against CSRF.

For your problem, this means that the two don't go together. If you want the convenience of a "normal" browser request (with its progress bar), you expose the user to CSRF, unless you take said countermeasures. (This may not be a serious threat if the request only downloads something. But an attacker could at least measure the running time of the CV download request and deduce something from that.)

Possible workaround:

Implement an API (authenticated with Authorization: Bearer header) that generates a download URL which includes a short-lived token, and have the browser download the desired document with a normal request to that URL. The second request is technically unauthenticated, but the token makes the URL unguessable.

If the token is a JWT, you need not even store it on your server but can simply write the username and expiry time in it and sign it with the server's private key. If you prefer you can also put the token in a cookie rather than in the URL.

Sorb answered 10/6, 2022 at 14:52 Comment(3)
Off the record, What do you think about my download method at frontend. Do you recommend me another option.Makell
I like your idea of re-using browser functionality like the progress bar. But I also like CSRF protection, that's why I suggested the workaround.Zea
This is very annoyingWolfram

© 2022 - 2024 — McMap. All rights reserved.