Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Andrew Hartnett
Andrew Hartnett
5,242 Points

Khan Academy API authentication in Node.js

I am trying to use the Khan Academy API and I would like use node.js. However I cannot for the life of me get the authentication to work.

https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication

I have tried everything I can think of, from different npm OAuth modules to trying to write the http.request by hand.

Any help would be greatly appreciated.

First up, are you trying to just authenticate using your own Khan Academy account? Because those docs mention that you can send a POST request with your username and password in the body (pretty much like filling out a login form).

Second, if you're not, then you need to be able to display a page with the authorisation form to the user.

Can you post your code or share it if you already have it on GitHub or something?

Don't forget to hide/remove your API key or username & password if you have that in your code somewhere.

3 Answers

Andrew Hartnett
Andrew Hartnett
5,242 Points

Iain,

I am trying to authenticate my own account. I have attached the code below. The first post (to get the oauth_token) goes through fine. The second one however, returns a status 400 and the following message:

Response status 400 Response body The oauth_token parameter must be specified.

which is odd because that is specified in the documentation as requested. Any help would be greatly appreciated.

var request = require('request');
var qs = require('querystring');

var consumerKey = '';
var secretKey = '';
var email = '';
var pwd = '';

var getTokenURL = 'https://www.khanacademy.org/api/auth2/request_token';
var authorizeURL  = 'https://www.khanacademy.org/api/auth2/authorize';

var httpMethod = 'POST';
var nativeOAuthOptions = {
    consumer_key: consumerKey,
    consumer_secret: secretKey
}

var req = request.post({url:getTokenURL, oauth: nativeOAuthOptions}, function (e, rsp, body) {
    console.log("Response status", rsp.statusCode);
    console.log("Response body", rsp.body);

    // now I can pull out the oauth_token and use it in the next post to authorize
    // req_data.oauth_token is indeed a string
    var req_data = qs.parse(body);
    console.dir(req_data);
    console.log(typeof req_data.oauth_token);

    var bodyParams = {
        oauth_token: req_data.oauth_token,
        identifier: email,
        password: pwd
    };
    console.log(JSON.stringify(bodyParams));

    // first reqest goes through fine - and I get back an oauth_token
    // this one below returns status 400
    // REQUEST end event https://www.khanacademy.org/api/auth2/authorize
    // REQUEST has body https://www.khanacademy.org/api/auth2/authorize 44
    // REQUEST emitting complete https://www.khanacademy.org/api/auth2/authorize
    // Response status 400
    // Response body The oauth_token parameter must be specified.

    request.post({url: authorizeURL, body: JSON.stringify(bodyParams)}, function (e2, rsp2, body2) {
       console.log("Response status", rsp2.statusCode);
       console.log("Response body", rsp2.body);
    });
});

Hmmm try sending the bodyParams through the form option in the second POST request, instead of a JSON string:

 request.post({url: authorizeURL, form: bodyParams}, function (e2, rsp2, body2) {
       console.log("Response status", rsp2.statusCode);
       console.log("Response body", rsp2.body);
    });

Or use json instead of body or form:

 request.post({url: authorizeURL, json: bodyParams}, function (e2, rsp2, body2) {
       console.log("Response status", rsp2.statusCode);
       console.log("Response body", rsp2.body);
    });

I believe body will just send the string as you specify, but you're not specifying that you're sending JSON data in your request. Both form and json will send headers specifying the format that the POST body is in.

Andrew Hartnett
Andrew Hartnett
5,242 Points

There turned out to be two important fixes. The first was to make it a form as you suggested. The second was to enable multiple redirects.

I can now fully authenticate and make API calls. All API calls work fine except for one. It turns out the data coming back is too large.

request.get({uri: URI, baseUrl: baseURL, qs: QS, oauth: oauthParams}, function (e4, rsp4, body4) {
                console.log("Response status", rsp4.statusCode);
                console.dir(JSON.parse(rsp4.body));
});

How do I deal with too much data coming back?

Thanks, Andrew

Really? That seems strange... Do you get an error message that says the response is too large?

Anyways, you might want to look at streaming the response rather than waiting for the entire thing.

Also, I think you can get a parsed copy of the JSON response straight from the request library, rather than having to use JSON.parse()...

Andrew Hartnett
Andrew Hartnett
5,242 Points

Yes. The error is:

Response status 500
undefined:1
HTTP response was too large: 35466886. The limit is: 33554432.
^
SyntaxError: Unexpected token H
    at Object.parse (native)
    at Request._callback (/Users/andrewhartnett/Documents/git/dgibson_khan/khan2.js:111:34)
    at Request.self.callback (/Users/andrewhartnett/node_modules/request/request.js:198:22)
    at Request.emit (events.js:110:17)
    at Request.<anonymous> (/Users/andrewhartnett/node_modules/request/request.js:1073:14)
    at Request.emit (events.js:129:20)
    at IncomingMessage.<anonymous> (/Users/andrewhartnett/node_modules/request/request.js:1019:12)
    at IncomingMessage.emit (events.js:129:20)
    at _stream_readable.js:908:16
    at process._tickCallback (node.js:355:11)

All other api calls work fine.

As far as streaming the response, could you provide some basic pseudo code for that? Do I just open a stream, write, and then close? I am struggling with the documentation on streaming.

This should just 'pipe' the response straight to a file (that is, any data received from the readable stream created by request is immediately sent to the writable stream created in this case for a JSON file, just so you can see what is coming through).

var fs = require('fs');
request.get({uri: URI, baseUrl: baseURL, qs: QS, oauth: oauthParams})
  .on('error', function(err) {
    console.log(err)
  })
  .pipe(fs.createWriteStream('khan_resp_4.json'));

Once you've got that going, you can open the JSON file and figure out why it's so huge! Is there a bunch of image files you're trying to get or something?

Oh, and try console.dir(JSON.parse(body4)); first instead of console.dir(JSON.parse(rsp4.body));, just in case...

You could also try this, to just output the data to the console as it comes through:

var fs = require('fs');
request.get({uri: URI, baseUrl: baseURL, qs: QS, oauth: oauthParams})
  .on('error', function(err) {
    console.log(err);
  })
  .on('data', function(data) {
    console.log(data);
  })

Here's a cool site to play with Streams: Node.js Stream Playground. I noticed on there that there is a library JSONStream. That could be useful, since then you can just pipe the request straight to the JSONStream...