javascriptnode.jsoauthpassport.jsjawbone

Jawbone API OAuth access_token handling with node.js (express & passport)


Has anyone successfully navigated Jawbone's OAuth2.0 authentication for their REST API?

I am unable to figure out how to access and send the authorization_code in order to obtain the access_token (steps 4 & 5 in the Jawbone API Authorization Documentation). I want to reuse the access_token for subsequent (AJAX-style) calls and avoid asking the user to reauthorize each time.

Each call of the API (get.sleeps) requires a full round trip of the auth process including this reauthorization to get an authorization_token (screen shot). Both the Jawbone and Passport Documentation is vague on this point.

enter image description here

My stack involves, node.js, the jawbone-up NPM, express.js and passport.js. The Passport Strategy for Jawbone appears to work correctly as I get valid data back.

The jawbone-up NPM explicitly does not help maintain the session (access_token), saying "This library does not assist in getting an access_token through OAuth..."

QUESTION: how do I actually use the OAUTH access_token in the API call? Can someone show me some code to do this?

Thanks

var dotenv = require('dotenv').load(),
    express = require('express'),
    app = express(),
    ejs = require('ejs'),
    https = require('https'),
    fs = require('fs'),
    bodyParser = require('body-parser'),
    passport = require('passport'),
    JawboneStrategy = require('passport-oauth').OAuth2Strategy,
    port = 5000,
    jawboneAuth = {
       clientID: process.env.JAWBONE_CLIENT_ID,
       clientSecret: process.env.JAWBONE_CLIENT_SECRET,
       authorizationURL: process.env.JAWBONE_AUTH_URL,
       tokenURL: process.env.JAWBONE_AUTH_TOKEN_URL,
       callbackURL: process.env.JAWBONE_CALLBACK_URL 
    },
    sslOptions = {
        key: fs.readFileSync('./server.key'),
        cert: fs.readFileSync('./server.crt')
    };
    app.use(bodyParser.json());
    app.use(express.static(__dirname + '/public'));
    app.set('view engine', 'ejs');
    app.set('views', __dirname + '/views');

// ----- Passport set up ----- //
app.use(passport.initialize());

app.get('/', 
    passport.authorize('jawbone', {
        scope: ['basic_read','sleep_read'],
        failureRedirect: '/'
    })
);

app.get('/done',
    passport.authorize('jawbone', {
        scope: ['basic_read','sleep_read'],
        failureRedirect: '/'
    }), function(req, res) {
        res.render('userdata', req.account);
    }
);

passport.use('jawbone', new JawboneStrategy({
    clientID: jawboneAuth.clientID,
    clientSecret: jawboneAuth.clientSecret,
    authorizationURL: jawboneAuth.authorizationURL,
    tokenURL: jawboneAuth.tokenURL,
    callbackURL: jawboneAuth.callbackURL
}, function(token, refreshToken, profile, done) {
    var options = {
            access_token: token,
            client_id: jawboneAuth.clientID,
            client_secret: jawboneAuth.clientSecret
        },
        up = require('jawbone-up')(options);

    up.sleeps.get({}, function(err, body) {
        if (err) {
            console.log('Error receiving Jawbone UP data');
        } else {
        var jawboneData = JSON.parse(body).data;
        console.log(jawboneData);
        return done(null, jawboneData, console.log('Jawbone UP data ready to be displayed.'));
        }
    });
}));
// HTTPS
var secureServer = https.createServer(sslOptions, app).listen(port, function(){
    console.log('UP server listening on ' + port);
});

Solution

  • You weren't too far off, you were already getting the token. To make your code work a few steps are needed:

    Add the concept of a "session", data that exists from request to request as a global variable. When you do a full web app use express-sessions and passport-sessions and implement user management. But for now we just add a global for a single user state.

    var demoSession = {
        accessToken: '',
        refreshToken: ''
    };
    

    Pass in a user object in the done() of JawboneStrategy. This is because the "authorize" feature of passport is expecting a user to exist in the session. It attaches the authorize results to this user. Since we are just testing the API just pass in an empty user.

    // Setup the passport jawbone authorization strategy
    passport.use('jawbone', new JawboneStrategy({
        clientID: jawboneAuth.clientID,
        clientSecret: jawboneAuth.clientSecret,
        authorizationURL: jawboneAuth.authorizationURL,
        tokenURL: jawboneAuth.tokenURL,
        callbackURL: jawboneAuth.callbackURL
    }, function(accessToken, refreshToken, profile, done) {
        // we got the access token, store it in our temp session
        demoSession.accessToken = accessToken;
        demoSession.refreshToken = refreshToken;
        var user = {}; // <-- need empty user
        done(null, user);
        console.dir(demoSession);
    }));
    

    Use a special page to show the data "/data". Add a route to separate the auth from the display of service.

    app.get('/done', passport.authorize('jawbone', {
            scope: ['basic_read','sleep_read'],
            failureRedirect: '/'
        }), function(req, res) {
            res.redirect('/data');
        }
    );
    

    Lastly the Jawbone Up sleeps API is a little tricky. you have to add a YYYYMMDD string to the request:

    app.get('/data', function(req, res) {
    
        var options = {
            access_token: demoSession.accessToken,
            client_id: jawboneAuth.clientID,
            client_secret: jawboneAuth.clientSecret
        };
        var up = require('jawbone-up')(options);
    
        // we need to add date or sleep call fails
        var yyyymmdd = (new Date()).toISOString().slice(0, 10).replace(/-/g, "");
        console.log('Getting sleep for day ' + yyyymmdd);
    
        up.sleeps.get({date:yyyymmdd}, function(err, body) {
            if (err) {
                console.log('Error receiving Jawbone UP data');
            } else {
                try {
                    var result = JSON.parse(body);
                    console.log(result);
                    res.render('userdata', {
                        requestTime: result.meta.time,
                        jawboneData: JSON.stringify(result.data)
                    });
                }
                catch(err) {
                    res.render('userdata', {
                        requestTime: 0,
                        jawboneData: 'Unknown result'
                    });
                }
    
            }
        });
    });
    

    I have created a gist that works for me here thats based on your code: https://gist.github.com/longplay/65056061b68f730f1421