103

I am sending POST request like this from browser:

fetch(serverEndpoint, {
    method: 'POST',
    mode: 'no-cors', // this is to prevent browser from sending 'OPTIONS' method request first
    redirect: 'follow',
    headers: new Headers({
            'Content-Type': 'text/plain',
            'X-My-Custom-Header': 'value-v',
            'Authorization': 'Bearer ' + token,
    }),
    body: companyName
})

By the time the request reaches my back-end it does not contain X-My-Custom-Header nor Authorization header.

My back-end is Google Cloud function for Firebase (basically just Node.js endpoint) that looks like this:

exports.createCompany = functions.https.onRequest((req, res) => {
    let headers = ['Headers: ']
    for (let header in req.headers) {
        headers.push(`${header} : ${req.headers[header]}`)
    }
    console.log(headers)
    ...
}

The console log of that Google Cloud for Firebase function does not contain any X-My-Custom-Header nor Authorization header.

What is wrong?


Edit 1

So using dev tools in Chrome a checked that neither X-My-Custom-Header nor Authorization header is send from the browser... The questions now are: Why? How do I fix it?


Edit 2

More information about my app: It's React app. I have disabled service worker. I have tried to create Request and specifically add headers using req.headers.append(). The headers still wouldn't send.

Rasto
  • 17,204
  • 47
  • 154
  • 245
  • 1
    Is your browser actually sending the headers? Check your dev tools. – Joe Clay Aug 09 '17 at 13:10
  • @JoeClay I am seasoned developer (mobile, backend) but rather new to web front-end development. Many tools are new for me - especially dev tools in brownser are not very good friend of mine yet. Can you suggest how to I check it on Chrome or Safari? Thanks – Rasto Aug 09 '17 at 19:42
  • 2
    In Chrome, press F12 to open your dev tools, and then switch to the Network tab. When your application sends a HTTP request, it'll appear in the list, and you can click on it to view the headers/body of the request and response. See [the docs](https://developers.google.com/web/tools/chrome-devtools/) for more info - learning how to use your browser's dev tools will help you loads if you're just starting out with web development :) – Joe Clay Aug 10 '17 at 08:10
  • 1
    @JoeClay So the answer is **no** the browser does not send `X-My-Custom-Header` nor `Authorization`. Now the remaining questions are why? And how to fix it? – Rasto Aug 10 '17 at 16:45
  • See https://stackoverflow.com/questions/42311018/why-does-fetch-api-send-the-first-put-request-as-options/42311206#comment71821815_42311018 at [Why does Fetch API Send the first PUT request as OPTIONS](https://stackoverflow.com/q/42311018/) – guest271314 Aug 12 '17 at 18:55

5 Answers5

167

The same-origin policy restricts the kinds of requests that a Web page can send to resources from another origin.

In the no-cors mode, the browser is limited to sending “simple” requests — those with safelisted methods and safelisted headers only.

To send a cross-origin request with headers like Authorization and X-My-Custom-Header, you have to drop the no-cors mode and support preflight requests (OPTIONS).

The distinction between “simple” and “non-simple” requests is for historical reasons. Web pages could always perform some cross-origin requests through various means (such as creating and submitting a form), so when Web browsers introduced a principled means of sending cross-origin requests (cross-origin resource sharing, or CORS), it was decided that such “simple” requests could be exempt from the preflight OPTIONS check.

Vasiliy Faronov
  • 11,840
  • 2
  • 38
  • 49
  • 6
    Great answer, thank you. However, not the answer that would make me happy. I am trying to add support fro CORS now. – Rasto Aug 11 '17 at 17:41
  • @sideshowbarker yes, definitelly. It is. Just supporting CORS well does not seem to be very straightforward – Rasto Aug 11 '17 at 18:07
  • 1
    @sideshowbarker Have a look on my profile, its age, badges and older questions. From that you should understand that I know what I asked and how to mark aswer accepted, thank you. If you really want to advice others what to do with answers and bounties you can do it on meta. As for this answer, I might only mark it as accepted once I have CORS supported on server. Without that I have no proof that this really works (and 500 is a lot of rep to spend on incorrect answer). Also, as you should know I have no obligation to mark it as accepted even if it works as I may wait for more/better answers. – Rasto Aug 11 '17 at 18:27
  • The above being said, to avoid missunderstanding: I really like Vasiliy's answer for it's high quality and will most probably accepted if it works once I can get that CORS support working on back-end (Firebase functions). – Rasto Aug 11 '17 at 18:35
  • 1
    https://stackoverflow.com/questions/42755131/enabling-cors-in-cloud-functions-for-firebase This might help. – CaptainHere Aug 11 '17 at 21:35
  • This answers the questions "How to Fetch with Authorization header from Browser" https://stackoverflow.com/questions/49967188/using-fetch-with-authorization-header-and-cors – pungggi Oct 10 '18 at 13:29
51

Firstly : Use an object instead of new Headers(..):

fetch('www.example.net', {
  method: 'POST',
  headers: {
    'Content-Type': 'text/plain',
    'X-My-Custom-Header': 'value-v',
    'Authorization': 'Bearer ' + token,
  }
});

Secondly : Good to know, headers are lowercased by fetch!!

Thirdly : no-cors mode limits the use of headers to this white list :

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type and whose value is ( application/x-www-form-urlencoded, multipart/form-data, text/plain )

That's why only your Content-Type header is sent and not X-My-Custom-Header or Authorization.

Artur Carvalho
  • 6,901
  • 10
  • 76
  • 105
Damien
  • 3,915
  • 1
  • 17
  • 18
5

Can you try this?

fetch(serverEndpoint, {  
  credentials: 'include'  
})

Ref. https://developers.google.com/web/updates/2015/03/introduction-to-fetch#sending_credentials_with_a_fetch_request

Frank R.
  • 1,732
  • 18
  • 21
2

I also had this same issue. I resolved it by removing 'no-cors' from javascript and adding the following in server side spring boot.

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity httpSecurity) throws Exception {
             .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

        }
    }
Srdjan Dejanovic
  • 1,267
  • 2
  • 18
  • 31
Greeshma
  • 95
  • 2
  • 10
1

1st: when you call headers in your exports.createCompany function, you have let headers = ['Headers: '] with a capital H instead of lowercase h which might cause errors. you also have a comma after token in the headers which shouldn't be there.

2nd: everytime i have used fetch requests in react native, the header: doesn't need the new Headers on it.

try this:

fetch(serverEndpoint, {
    method: 'POST',
    mode: 'no-cors',
    redirect: 'follow',
    headers:{
      'Content-Type': 'text/plain',
      'X-My-Custom-Header': 'value-v',
      'Authorization': 'Bearer ' + token
    },
    body: companyName
})
David
  • 3,285
  • 1
  • 37
  • 54
Timmehlkk
  • 51
  • 1
  • 5
  • a note about the trailing comma, it's becoming a standard is ES2017 but people have been using it with Babel (https://babeljs.io/docs/plugins/syntax-trailing-function-commas/) and also NodeJS has supported it since version `6.4.0`. This shouldn't effect the OP as I've used trailing commas with `fetch()` before with success. – Sgnl Aug 17 '17 at 00:50
  • This answer does not address the first nor the second question. It just gives some syntax corrections. – pungggi Oct 10 '18 at 13:34