216

I'm trying to use fetch in React Native to grab information from the Product Hunt API. I've obtained the proper Access Token and have saved it to State, but don't seem to be able to pass it along within the Authorization header for a GET request.

Here's what I have so far:

var Products = React.createClass({
  getInitialState: function() {
    return {
      clientToken: false,
      loaded: false
    }
  },
  componentWillMount: function () {
    fetch(api.token.link, api.token.object)
      .then((response) => response.json())
      .then((responseData) => {
          console.log(responseData);
        this.setState({
          clientToken: responseData.access_token,
        });
      })
      .then(() => {
        this.getPosts();
      })
      .done();
  },
  getPosts: function() {
    var obj = {
      link: 'https://api.producthunt.com/v1/posts',
      object: {
        method: 'GET',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + this.state.clientToken,
          'Host': 'api.producthunt.com'
        }
      }
    }
    fetch(api.posts.link, obj)
      .then((response) => response.json())
      .then((responseData) => {
        console.log(responseData);
      })
      .done();
  },

The expectation I have for my code is the following:

  1. First, I will fetch an access token with data from my imported API module
  2. After that, I will set the clientToken property of this.state to equal the access token received.
  3. Then, I will run getPosts which should return a response containing an array of current posts from Product Hunt.

I am able to verify that the access token is being received and that this.state is receiving it as its clientToken property. I am also able to verify that getPosts is being run.

The error I'm receiving is the following:

{"error":"unauthorized_oauth", "error_description":"Please supply a valid access token. Refer to our api documentation about how to authorize an api request. Please also make sure you require the correct scopes. Eg \"private public\" for to access private endpoints."}

I've been working off the assumption that I'm somehow not passing along the access token properly in my authorization header, but don't seem to be able to figure out exactly why.

Chris Pickford
  • 8,642
  • 5
  • 42
  • 73
Richard Kho
  • 5,086
  • 4
  • 21
  • 35
  • 2
    As noted in [this SO](https://stackoverflow.com/a/5259004), headers are intended to be **lowercase** (some servers respect this, others do not.) I only share because I was bitten by it not knowing myself (and wasted time trying to debug the issue.) It is unfortunate that so many projects, examples, and articles do not seem to respect this. – t.j. May 25 '18 at 17:57
  • @t.j. Header names are not case sensitive, and that's exactly what the accepted + top answer says on the question you linked. – coreyward Jan 02 '19 at 22:33
  • 1
    5.5 years later and I was setting a fetch header just like the OP: `'Authorization': 'Bearer ' + myJWT,` Turns out myJWT was getting wrapped in double quotes! `Authorization: Bearer "yadda.yadda.yadda"` After many attempts at a solution, I filtered the double quotes on my back-end. – GuyWrySmile Jan 17 '21 at 19:08

5 Answers5

298

Example fetch with authorization header:

fetch('URL_GOES_HERE', { 
    method: 'post', 
    headers: new Headers({
        'Authorization': 'Basic '+btoa('username:password'), 
        'Content-Type': 'application/x-www-form-urlencoded'
    }), 
    body: 'A=1&B=2'
});
Torxed
  • 22,866
  • 14
  • 82
  • 131
Cody Moniz
  • 4,845
  • 3
  • 22
  • 20
  • 9
    This is not working for me. The `'Authorization'` header silently fails to attach per firebug. I've even tried including `credentials: 'include'` in the optional object. – Ronnie Royston Dec 06 '16 at 04:13
  • 8
    @RonRoyston are you looking at the OPTIONS call? if the API endpoint doesn't have CORS enabled (Access-Control-Allow-Origin: * if accessing from a different domain), then it might fail at the OPTIONS call. – Cody Moniz Dec 07 '16 at 23:49
  • 1
    api endpoint does not have CORS enabled, so that's probably why it didn't work for me. Thanks. I ended up installing 'cors everywhere' add-on for firefox and it worked. – Ronnie Royston Dec 08 '16 at 17:03
  • 4
    Re the issue @RonRoyston is seeing, **you need to import the btoa library**, which is not native to node. (It's a port of a browser lib.) Otherwise auth header creation silently fails. We were experiencing the same thing. – Freewalker Feb 24 '17 at 21:15
  • Shouldn't the Authorization header format for Token based authentication look like `Authorization: Token token=123123123`? [reference](https://www.codeschool.com/blog/2014/02/03/token-based-authentication-rails/) – Marklar Apr 21 '17 at 08:03
  • 3
    https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch per docs, need to wrap headers with `new Headers()` – Daniel Dror Mar 22 '18 at 14:39
  • @RonRoyston I used your 'cors everywhere' addon suggestion and was able to send the authorization header and get response. My question is: my application will be accessed by the customer and I do not want to ask them to download the addon in order to use my app. How can I solve my issue, without having to download the addon. – user1579234 Apr 18 '19 at 19:20
  • you will have to proxy the request from a secure server environment, such as a Node.js/Express.js server. – Ronnie Royston Apr 18 '19 at 19:30
  • @Marklar `Authorization: "Basic {}"` would mean `Authorization: "Token 123123123"` – CDM social medias in bio Aug 15 '19 at 03:52
  • Note: If "grant_type=client_credentials" is part of your request, the "Basic" token is likely not supposed to be accessible to the public. Client credentials are used for machine-to-machine communication. More info here: https://auth0.com/docs/applications/confidential-and-public-applications – Cody Moniz Aug 25 '20 at 17:52
  • VSCode says `The signature '(data: string): string' of 'btoa' is deprecated.` with TypeScript – kaushalpranav Nov 15 '21 at 08:52
  • @kaushalpranav apparently you need to use `window.atob` instead https://github.com/microsoft/TypeScript/issues/45566#issuecomment-905059122 – Cody Moniz Nov 16 '21 at 20:50
  • could you please check my post here. Was not able to get an answer until now.. https://stackoverflow.com/questions/75235933/flask-jwt-extended-exceptions-noauthorizationerror-missing-authorization-header – 3awny Feb 01 '23 at 11:32
97

It turns out I was using the fetch method incorrectly.

fetch accepts two parameters: an endpoint to the API, and an optional object which can contain body and headers.

I was wrapping the intended object within a second object, which did not get me any desired result.

Here's how it looks on a high level:

    fetch('API_ENDPOINT', options)  
      .then(function(res) {
        return res.json();
       })
      .then(function(resJson) {
        return resJson;
       })

I structured my options object as follows:

    var options = {  
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Origin': '',
        'Host': 'api.producthunt.com'
      },
      body: JSON.stringify({
        'client_id': '(API KEY)',
        'client_secret': '(API SECRET)',
        'grant_type': 'client_credentials'
      })
    }
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
Richard Kho
  • 5,086
  • 4
  • 21
  • 35
  • could you perhaps provide the now working code? I'm trying to use fetch with an authorization header and I don't think my auth code is being passed as a header, because I'm getting a `401` response. – MikaelC Aug 04 '15 at 22:53
  • 2
    Done, hope it's helpful – Richard Kho Aug 04 '15 at 22:55
  • 1
    Oh i've been on your personal site with that example! That's how I modeled mine the first time. I figured out my issue though, it was just that my url was wrong. It required a `/` at the end that I was missing... – MikaelC Aug 05 '15 at 00:42
  • 1
    Thanks, this was helpful. Worth noting that while the fetch documentation points out that fetch doesn't handle cookies, you can manually add cookies to the header with this code as well. Just save the uid and key and do something like: var obj = { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Cookie': 'uid='+uid+'; key='+key }); – Dustin Oct 23 '15 at 13:44
  • @RichardKho could you please check my post here. Was not able to get an answer until now.. https://stackoverflow.com/questions/75235933/flask-jwt-extended-exceptions-noauthorizationerror-missing-authorization-header – 3awny Feb 01 '23 at 11:31
25

The following code snippet should work if you're using the bearer token :

const token = localStorage.getItem('token')

const response = await fetch(apiURL, {
        method: 'POST',
        headers: {
            'Content-type': 'application/json',
            'Authorization': `Bearer ${token}`, // notice the Bearer before your token
        },
        body: JSON.stringify(yourNewData)
    })
user8572385
  • 457
  • 4
  • 6
15

I had this identical problem, I was using django-rest-knox for authentication tokens. It turns out that nothing was wrong with my fetch method which looked like this:

...
    let headers = {"Content-Type": "application/json"};
    if (token) {
      headers["Authorization"] = `Token ${token}`;
    }
    return fetch("/api/instruments/", {headers,})
      .then(res => {
...

I was running apache.

What solved this problem for me was changing WSGIPassAuthorization to 'On' in wsgi.conf.

I had a Django app deployed on AWS EC2, and I used Elastic Beanstalk to manage my application, so in the django.config, I did this:

container_commands:
  01wsgipass:
    command: 'echo "WSGIPassAuthorization On" >> ../wsgi.conf'
Marquistador
  • 1,841
  • 19
  • 26
-7
completed = (id) => {
    var details = {
        'id': id,

    };

    var formBody = [];
    for (var property in details) {
        var encodedKey = encodeURIComponent(property);
        var encodedValue = encodeURIComponent(details[property]);
        formBody.push(encodedKey + "=" + encodedValue);
    }
    formBody = formBody.join("&");

    fetch(markcompleted, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: formBody
    })
        .then((response) => response.json())
        .then((responseJson) => {
            console.log(responseJson, 'res JSON');
            if (responseJson.status == "success") {
                console.log(this.state);
                alert("your todolist is completed!!");
            }
        })
        .catch((error) => {
            console.error(error);
        });
};
lilash sah
  • 97
  • 1
  • 1