0
votes

THE ANSWER TO THIS QUESTION WAS IN THE FACT THAT I WAS USING AUTHORIZATION CODE GRANT FLOW ON THE CLIENT SIDE.. THIS SEEMS TO RESULT IN BLOCKING ERROR..

CHECK UNDERNEATH FOR FUNCTIONING AUTHENTICATION OAUTH2 IMPLICIT GRANT FOR FITBIT API..

I am performing OAuth2 authentication against the Fitbit API. This all using the Authorization Code Grant Flow. So first getting the auth code, being redirected to my application, then exchanging this for the access token and getting data with this token.

Starting off at the homePage on the "post_request.html" page, pushing the "fitbit" button, the user is redirected to the Authorization EndPoint of Fitbit. I am using Node.js to build a localserver to host the application and to be able to redirect without any problem..

On being redirected, I retrieve the authorization code and make an AJAX POST request to trade the authorization code for an access token. I then make this POST request but I get the following error..

{"errors":[{"errorType":"invalid_client","message":"Invalid authorization header format. The header was not recognized to be a valid header for any of known implementations or a client_id was not specified in case of a public client Received header = BasicdW5kZWZpbmVk. Visit https://dev.fitbit.com/docs/oauth2 for more information on the Fitbit Web API authorization process."}],"success":false}

I think there might be an error in me url encoding the client_id and client_secret. Or in me setting the headers. But I cannot spot it. Can anyone help me out?

My HTML file is the following..

<body>

    <button onclick="fitbitAuth()">Fitbit</button>

    <!-- action = route, method = method -->
    <form action="/" method="POST" id="form">
        <h3>Email Address:</h3>
        <input type="email">
        <br>
        <h3>Password:</h3>
        <input type="password">     
        <br>
        <br>
        <button type="submit">Send Request</button>   
    </form>

</body>
</html>

My script is the following, it consists of 3 functions.. - base64 encoding function - onclick function for starting the OAuth2 process - function that trades in auth code for access token

// run this script upon landing back on the page with the authorization code 
            // specify and/ or calculate parameters 
            var url_terug = window.location.search;
            var auth_code = url_terug.substr(6);
            var granttype = "authorization_code";
            var redirect_uri = "http://localhost:3000/fitbit";
            var client_id = "xxxxx";
            var client_secret = "xxxxxxxxxxxx";
            var stringto_encode = client_id + ":" + client_secret;
            var encoded_string = "";
            console.log("auth code = " + auth_code);

            function baseEncode(stringto_encode){

                encoded_string = btoa(stringto_encode);
                console.log(encoded_string);

            }

            function getTokenFitBit(){

                baseEncode();

                // execute a POST request with the right parameters 
                var request = new XMLHttpRequest();
                request.open('POST', "https://api.fitbit.com/oauth2/token?client_id=" + client_id + "&grant_type=" + granttype + "&redirect_uri=" + redirect_uri + "&code=" + auth_code);

                request.setRequestHeader('Authorization', 'Basic'+ encoded_string);
                request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

                // Setup our listener to process completed requests
                request.onload = function () {

                    // Process our return data
                    // status code between 200 and 300 indicates success
                    if (request.status >= 200 && request.status < 300) {

                        console.log('success!', request);
                        console.log(request.responseText);

                        // continue with API calls.. 


                    // you could set up a broader response handling if there is a use case for it
                    } else {

                        console.log('The current token request failed!');

                    }
                };

                request.send();                    

            }

            getTokenFitBit();




            // get the access token out of the JSON response 
            // execute a GET request on the API endpoint 
            // handle the data 

          // upon clicking fitbit button, starting off the oauth2 authentication 
          function fitbitAuth() {

                window.location.href = 'https://www.fitbit.com/oauth2/authorize?client_id=xxxxx&response_type=code&scope=activity&redirect_uri=http://localhost:3000/fitbit&prompt=consent';

            }
2

2 Answers

1
votes

In the above example, you appear to have missing a separator between the "Basic" header and it's value in this line:

  request.setRequestHeader('Authorization', 'Basic'+ encoded_string);

Provided you are building a web based applications, you may want to look into using the 'Implicit' Flow, too: https://oauth.net/2/grant-types/implicit/

0
votes

The following code functions and uses the implicit grant flow. It works with FitBit API, Node.js to redirect to the application, and then client-side authentication.

Code for the Node.js local server module

// PROJECT authenticating Fitbit OAuth2 Implicit Grant  
const express = require("express");
const app = express();
const filesys = require("fs");
const path = require("path");
// body parser module parses form data into server
const body_parse = require("body-parser");

// middleware to encrypt folder structure for users : can look into DevTools 
app.use('/public', express.static(path.join(__dirname, 'static')));
// allows us to parse url encoded forms 
app.use(body_parse.urlencoded({extended: false}));

// using readstream with chunks in buffer with security on the path 
app.get("/fitbit", (req, res) => {

    const readStream = filesys.createReadStream(path.join(__dirname,'static','post_request.html'));
    readStream.on('error', function(){

        /*handle error*/ 
        res.write("there is an error authenticating against fitbit api endpoint");

        });
    res.writeHead(200, {'Content-type' : 'text/html'});
    readStream.pipe(res);

});

// bodyparser parses data and adds to the body of the request 
app.get("/", (req, res, err) => {

    const readStream = filesys.createReadStream(path.join(__dirname,'static','start_page.html'));
    res.writeHead(200, {'Content-type' : 'text/html'});
    readStream.pipe(res);

});

app.listen(3000);

HTML file for the start page, with just a button to initiate the OAuth2 process..

<body>

    <button onclick="fitbitAuth()">Fitbit</button>

<script>
        // define variables 
        var cli_id = "xxxxx";
        var res_typ = "token";
        var scope_typ = "activity";
        var redirect = "http://localhost:3000/fitbit";
        var expires = "31536000";
        var prompt_var = "consent";

        // upon clicking fitbit button, starting off the oauth2 authentication 
        function fitbitAuth() {

            window.location.href = "https://www.fitbit.com/oauth2/authorize?client_id=" + cli_id + "&response_type=" + res_typ + "&scope=" + scope_typ + "&redirect_uri=" + redirect + "&expires_in=" + expires + "&prompt=" + prompt_var;

        }

    </script>

</body>
</html>

The redirect page, the page the user gets sent back to after consenting using his/ her data.. With just one button to initiate API call on lifetime activity stats..

<body>

    <!-- action = route, method = method -->
    <button type="submit" onclick="getActivityData()">Send Request</button>   


<script>

        // get out the accesstoken from the provided data 
        var url_terug = window.location.href;
        var split_after = "access_token=";
        var split_before = "&user_id";
        var after_string = url_terug.split(split_after).pop();
        var accesstoken = after_string.slice(0, after_string.indexOf(split_before));
        console.log(accesstoken);

        // getActivityData();

        function getActivityData(){

            // execute a POST request with the right parameters 
            var request = new XMLHttpRequest();
            request.open('GET', "https://api.fitbit.com/1/user/-/activities.json");

            request.setRequestHeader('Authorization', 'Bearer '+ accesstoken);

            // Setup our listener to process completed requests
            request.onload = function () {

                // Process our return data
                // status code between 200 and 300 indicates success
                if (request.status >= 200 && request.status < 300) {

                    console.log('success!', request);
                    console.log(request.responseText);

                    // continue with API calls.. 


                // you could set up a broader response handling if there is a use case for it
                } else {

                    console.log('The current token request failed!');

                }
            };

            request.send();                    

        }

    </script>

</body>