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.
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);
});
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