2

I'm trying to send a POST request to a URL, using fetch().

I'm deliberately setting the Authorization header but when I inspect the response in Firefox Dev Tools it outputs following error "Missing request header 'Authorization' for method parameter of type String".

        var target_url = "https://api.sonos.com/login/v3/oauth/access";
        var encoded_msg = btoa(client_id + ':' + secret); // base64-encodes client_id and secret using semicolon as delimiter
        var params = `grant_type=authorization_code` + `&code=${authCode}` + `&redirect_uri=${redirect_uri}`;
        var myHeaders = new Headers({
            'Authorization': `Basic ${encoded_msg}`,
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'POST',
            'Content-Length': params.length,
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        });
    
        fetch(target_url, {
            method: 'POST',
            mode: 'no-cors',
            credentials: 'include',
            redirect: 'follow',
            headers: myHeaders,
            body: params
        })
        .then(response => {
            console.log("Status: " + response.status);
            console.log("StatusText: " + response.statusText);
            console.log("Type: " + response.type);
            console.log("URL: " + response.url);
        });

What removes the Authorization-Header, why and how do I prevent it?

Edit:
For Clarification, I'm using Firebase Cloud Functions to host my webpage from which I send the request to the Sonos Authorization API.
Using Postman, the request goes through and I get the correct response.

noah_s
  • 43
  • 1
  • 8
  • Hi, I know that you are quite new, but I have been in your shoes. The question is well formulated, but you are lost and you don't know where to look. In order to respond your question... I don't know the set up that you have so I can't tell. But what I can tell you is to use postman to debug (https://www.postman.com/), what I would do is send the request from postman to see if the error is in the server or in your client, if you are sending the request with postman with the authorization header, then it is server issue, contact the server people – Iria Jul 23 '20 at 09:06
  • You need to add Bearer instead of Basic. Is this Basic intentional? – Velu S Gautam Jul 23 '20 at 09:07
  • then I would try to see the request, debugging it. Try to use console.log(this), to see all of the parameters that you are sending, right before sending it – Iria Jul 23 '20 at 09:08
  • Bearer or basic depends on the server config, I am currently dealing with basic and it works – Iria Jul 23 '20 at 09:08
  • @VeluSGautam I'm using Basic because it's a requirement from the API I'm working with . – noah_s Jul 23 '20 at 09:11
  • Remove `mode: 'no-cors'` line. [this may helpful.](https://stackoverflow.com/questions/45591594/fetch-does-not-send-headers) – Alex Jul 23 '20 at 09:13
  • console log the request to see what you are actually sending – Iria Jul 23 '20 at 09:16
  • @AlexandrBiship When I remove `mode: 'no-cors'` I get a CORS error. – noah_s Jul 23 '20 at 09:24
  • @Iria, so if I use Postman and use the authorization header, then I get the correct response – noah_s Jul 23 '20 at 09:29
  • I think you maybe need to add the server url or test url(localhost) into the allowed javascript origin in your app when creating app on sonos. this will prevent cors error. – Alex Jul 23 '20 at 09:37
  • Removing `no-cors` is the key; getting a CORS error means the API can't be used from in-browser JS. (if all you had to do is add no-cors to your request, preventing CORS would be entirely pointless in the first place) This essentially means you need a backend that makes the requests, and forward relevant data to your web app. –  Jul 23 '20 at 09:38
  • @ChrisG Can I use a locally hosted webpage using XAMPP to make the request to the Firebase app and forward it to the API? – noah_s Jul 23 '20 at 09:43
  • @noah_s, you need to add the server url into allowed javascript origin on sonos for webapp. – Alex Jul 23 '20 at 09:44
  • Not sure what you mean by that, but you can make the API request from PHP. –  Jul 23 '20 at 09:45
  • @noah, a silly mistake that I have done many, many times... is to misspell some words, the best way to find out is to text compare the header that you are sending from postman (the one that works) with the one that you get from console.log – Iria Jul 23 '20 at 09:47
  • 1
    I ran into questions like this here before, and ended up writing [this article about no-cors](https://evertpot.com/no-cors/) you might find it helpful! – Evert Jul 23 '20 at 12:22

1 Answers1

1

The step you're performing to retrieve an access token must be performed in a Cloud function endpoint.

Get an access token
Once you’ve gotten an authorization code, use it to get an access token and refresh token. Use the access token to send API calls to the household through the Sonos cloud. This step uses your client secret, so it should be a server-side request rather than a client-side (browser-based) request.

Reference: https://developer.sonos.com/build/direct-control/authorize/

Bring in node-fetch as a dependency in your package.json because it's API implementation follows closely with browser fetch.

Add an endpoint as follows:


const fetch = require('node-fetch');
const functions = require('firebase-functions');

const secret = functions.config().sonos.secret;
const client_id = functions.config().sonos.client_id;
const redirect_uri = functions.config().sonos.redirect_uri;

exports.retrieveAccessToken = functions.https.onRequest(async (req, res) => {
        const {authCode} = req.query;
        const target_url = "https://api.sonos.com/login/v3/oauth/access";
        const encoded_msg = btoa(`${client_id}:${secret}`); // base64-encodes client_id and secret using semicolon as delimiter
        const body = `grant_type=authorization_code&code=${authCode}&redirect_uri=${redirect_uri}`;
        const headers = new Headers({
            'Authorization': `Basic ${encoded_msg}`,
            'Content-Length': body.length,
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        });
    
        const response = await fetch(target_url, {
            method: 'POST',
            redirect: 'follow',
            headers,
            body
        });

       const token_data = await response.json();
       return token_data;
});

Modify code in your webpage to make a request to the cloud function endpoint after user returns from Sonos login service.

 
 const authCode = new URLSearchParams(window.location.search).get('code');

 fetch(`https://us-central1-<project-id>.cloudfunctions.net/retrieveAccessToken?authCode=${code}`);

Oluwafemi Sule
  • 36,144
  • 1
  • 56
  • 81