154

I'm in a Google Chrome extension with permissions for "*://*/*" and I'm trying to make the switch from XMLHttpRequest to the Fetch API.

The extension stores user-input login data that used to be put directly into the XHR's open() call for HTTP Auth, but under Fetch can no longer be used directly as a parameter. For HTTP Basic Auth, circumventing this limitation is trivial, as you can manually set an Authorization header:

fetch(url, {
  headers: new Headers({ 'Authorization': 'Basic ' + btoa(login + ':' + pass) })
  } });

HTTP Digest Auth however requires more interactivity; you need to read the parameters that the server sends you with its 401 response to craft a valid authorization token. I've tried reading the WWW-Authenticate response header field with this snippet:

fetch(url).then(function(resp) {
  resp.headers.forEach(function(val, key) { console.log(key + ' -> ' + val); });
})

But all I get is this output:

content-type -> text/html; charset=iso-8859-1

Which by itself is correct, but that's still missing around 6 more fields according to Chrome's Developer Tools. If I use resp.headers.get("WWW-Authenticate") (or any of the other fields for that matter), I get only null.

Any chance of getting to those other fields using the Fetch API?

M Imam Pratama
  • 998
  • 11
  • 26
jules
  • 1,658
  • 2
  • 12
  • 10

9 Answers9

169

There is a restriction to access response headers when you are using Fetch API over CORS. Due to this restriction, you can access only following standard headers:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

When you are writing code for Google Chrome extension, you are using CORS, hence you can't access all headers. If you control the server, you can return custom information in the response body instead of headers

More info on this restriction - https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types

Divyanshu Maithani
  • 13,908
  • 2
  • 36
  • 47
Raj
  • 2,642
  • 3
  • 22
  • 20
  • 39
    @jules This restriction for CORS respects the values in `access-control-expose-headers`—or possibly `access-control-allow-headers` (we put it in both). – Jakob Jingleheimer Aug 21 '17 at 21:51
  • 25
    `access-control-expose-headers` worked for me for headers returned from server - then headers are available via the fetch response Headers object. And `access-control-allow-headers` was used to allow Request headers on the server (or I'd get an error message from server) – specimen Nov 14 '17 at 13:19
  • 14
    It's kind of stupid that this not possible with Fetch, but can be done with XmlHttpRequest. What is the security advantage if it's still possible with a workaround? – sebas Feb 21 '18 at 14:03
  • 3
    @sebas it looks like Chrome (and probably other browsers) impose the same restriction on `XmlHttpRequest`. – Benjineer Jul 07 '18 at 09:59
  • 1
    @benjineer Not sure about the general case, but in case of Chrome Extensions (which the original question was about), the Fetch headers are filtered while the XHR headers are all available. – sebas Jul 07 '18 at 19:55
  • 15
    Set `Access-Control-Allow-Headers` when allowing headers to be passed from the client to the server (e.g. `If-Match`). Set `Access-Control-Expose-Headers` when allowing headers to be passed back from the server to the client (e.g. `ETag`). – Dave Stevens Aug 28 '19 at 15:38
  • 1
    @sebas, this answer is outdated. As of Chrome 78 (October 2019) Chrome extensions can access all headers using the Fetch API, with few exceptions such as the `set-cookie` header. – GetFree Oct 03 '20 at 21:45
  • @GetFree, is this actually true? Can you confirm? If so post an answer please with details, thank you! – foba Mar 13 '21 at 00:26
  • @foba, the question is about Chrome extensions. `fetch()` will give access to the headers when used from a Chrome extension that has permission to access the target origin. In the past, this wasn't the case, but it was fixed in Chrome 78. – GetFree Mar 13 '21 at 17:16
  • The solution is a bit silly, headers can still be accessed if you explicitly set it to be accessible on your backend. – Steve Moretz May 18 '21 at 21:07
  • 1
    If you try to fix this with `'Access-Control-Expose-Headers': '*'` note that the * wildcard does not work if the request includes Authentication. You need to call out the header you are trying to access by name. – Carson Evans Apr 19 '22 at 06:18
  • @CarsonEvans Thank you so much! I saw the header in the dev console of the browser but not when I did a console.log in my code, after adding the header explicitly to the `'Access-Control-Expose-Headers': '*'` it finally worked! – funfried Feb 21 '23 at 14:19
88

If it's NOT CORS:

Fetch does not show headers while debugging or if you console.log(response.headers).

You have to use following way to access headers.

fetch(url).then(resp=>{
    console.log(resp.headers.get('x-auth-token'));
})

// or
fetch(url).then(resp=>{
    console.log(...resp.headers);
})

ahuigo
  • 2,929
  • 2
  • 25
  • 45
Nitin Jadhav
  • 6,495
  • 1
  • 46
  • 48
37

The Problem:

You may think this is a Frontend Problem.
It is a backend problem.
The browser will not allow to expose the Authorization header, unless if the Backend told the browser to expose it explicitly.

How To Solve It:

This worked for me.
In the backend (The API), add this to the response header:

response.headers.add("Access-Control-Expose-Headers","Authorization")

Why?

Security.
To prevent XSS exploits.
This request is supposed to be from backend to backend.
And the backend will set up the httpOnly cookie to the frontend.
So the authorization header should not be accessible by any third party JS package on your website.
If you think that it safe is to make the header accessible by frontend, do it.
But I recommend HttpOnly Cookies set up by the server backend to your browser immediately.

Reference:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

Omar Magdy
  • 2,331
  • 14
  • 13
33

From MDN

You can also get all the headers by accessing the entries Iterator.

// Display the key/value pairs
for (var pair of res.headers.entries()) {
   console.log(pair[0]+ ': '+ pair[1]);
}

Also, keep in mind this part:

For security reasons, some headers can only be controlled by the user agent. These headers include the forbidden header names and forbidden response header names.

Avram Tudor
  • 1,468
  • 12
  • 18
  • 3
    using the iterator presents the same output; only the content-type field. and the lists of forbidden header names only seem to apply to modification, and `WWW-Authenticate` isn't listed in either. – jules Apr 11 '17 at 12:37
  • This is the actual answer. Additionally using Waterfox Classic with the cors_everywhere-18.11.13.2043-fx extension (will post it on Fixed Firefox website) I can easily turn off CORS whenever I want and capture all of the headers. Thank you for posting, up-voted. – John Jun 07 '22 at 06:55
  • 1
    `console.log(Object.fromEntries(res.headers.entries()));` – Dem Pilafian Mar 08 '23 at 07:00
  • `session` cookie not there – ishandutta2007 Aug 25 '23 at 07:20
17

For backward compatibility with browsers that do not support ES2015 iterators (and probably also need fetch/Promise polyfills), the Headers.forEach function is the best option:

r.headers.forEach(function(value, name) {
    console.log(name + ": " + value);
});

Tested in IE11 with Bluebird as Promise polyfill and whatwg-fetch as fetch polyfill. Headers.entries(), Headers.keys() and Headers.values() does not work.

NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
7

For us to fix this restriction issue, adding exposed header names is good enough.

access-control-expose-headers: headername1, headername2, ...

After setting this header, the client side script is able to read those headers (headername1, headername2, ...) from the response.

Sheng
  • 794
  • 8
  • 12
1

In response to a cross-origin request, add 'Access-Control-Expose-Headers': '*' to your response header, so that all headers are available to be read in your client side code. You can also indicate which headers you want to expose by specifying the header names instead of a wildcard.

Note that per MDN the '*' wildcard is treated as a literal if the URL you are accessing has "credentials".

Carson Evans
  • 1,518
  • 1
  • 13
  • 12
antipodally
  • 474
  • 3
  • 5
-1

If you're using .net in Program.cs file or Startup.cs depending on the .net version you have to do something like this:

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
    builder =>
    {
        builder
            .SetIsOriginAllowed(p => true)
            .WithOrigins("http://localhost:3000", "http://*:3000")
            .AllowCredentials()
            .WithExposedHeaders("X-Pagination") /*custom header*/
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

}

-1

I'll compile my solution that is all from above:

For get header use:

response.headers.get('ex-token')

In order to this to work, we must set on backend api exposeHeader('ex-token')`

With Java Spring became like this:

        configuration.addExposedHeader("ex-token");
Sham Fiorin
  • 403
  • 4
  • 16