PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Sunday, September 4, 2022

[FIXED] How can I download a file from frontend with Authorization Header

 September 04, 2022     authentication, bearer-token, cookies, download, express     No comments   

Issue

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>


Solution

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.



Answered By - Heiko Theißen
Answer Checked By - David Marino (PHPFixing Volunteer)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing