6
$\begingroup$

About the project

I’m am building an online dashboard for my students that shows their up-to-the-minute Khan Academy progress. This will require access to the Khan Academy API. I’d like to build the whole thing in Mathematica.

Last school year I downloaded 13 unique spreadsheets and built some kludgy [Visual Basic][2] to bring it all together. This year, I'd like to make use of Khan's API and automate it all. Here’s an example report: Dropbox - khanReport.xlsx

What’s working:

I can make unauthenticated calls with success:

badges = Dataset[URLExecute["http://www.khanacademy.org/api/v1/badges”]]

I followed this Create A Random Nonce String Using JavaScript and made

choices = {“A”, “B”, “C”, “D”, "E", "F", "G", "H", "I", "J", "K", "L", 
           "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", 
           "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", 
           "m", "n", "o", "p", "q", "r", "t", "s", "u", "v", "w", "x", "y", 
           "z", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

MakeNONCE[vals0_, bits0_] := 
         Module[{vals = vals0, bits = bits0, result = ""}, 
          For[i = 0, i < bits - 1, i++, 
           result = StringJoin[result, ToString[RandomChoice[vals]]]];
          result
          ]

MakeNONCE[choices, 64]

I’ve written this, which I believe is formatted correctly but I still cannot generate a few values:

params = {"oauth_consumer_key" -> "7cPJxRmA5ybuaNQz", 
   "oauth_nonce" -> MakeNONCE[choices, 64], "oauth_version" -> "1.0", 
   "oauth_signature" -> "abc", 
   "oauth_signature_method" -> "HMAC-SHA1", 
   "oauth_timestamp" -> UnixTime[]};
URLExecute["https://www.khanacademy.org/api/auth2/request_token", \
params]

What I still need help with:

  1. I cannot generate the signature correctly. I know that Hash[stuff,”SHA”] is close, but I’m uncertain what stuff needs to be included in the hash.
  2. Does my nonce code look right? Does it make sense? I’m certain there are built-in functions that would make this easy, but I couldn’t make them work.
$\endgroup$
  • 1
    $\begingroup$ I'm not an expert on OAuth, but think you're not giving us enough information. Obviously you should share your keys, but what code have you tried, what does it actually do, and what code would you like to make work? $\endgroup$ – Itai Seggev Jul 20 '17 at 3:08
  • $\begingroup$ @DavidG.Stork can you review my new post at your convenience? Thank you $\endgroup$ – Matt Green Jul 20 '17 at 16:45
  • $\begingroup$ At this point this question is not organized in a meaningful and informative way. I think the "Update..." and "Edit" sections should be (i) posted as an answer, or (ii) removed. $\endgroup$ – Anton Antonov Jul 22 '17 at 21:57
  • $\begingroup$ Ok, will do. Thanks $\endgroup$ – Matt Green Jul 23 '17 at 2:09
11
$\begingroup$

Update: Full Flow

So after much wrangling here is the full OAuth 1.0 flow.

(* Get the token *)

$consumerKey = "key";
$consumerSecret = "secret";

$oauthTokenResponseString =
 URLRead[
  OAuthSigning`Private`HMACSha1SignatureService[
   "http://www.khanacademy.org/api/auth2/request_token",
   "HMAC", "GET",
   $consumerKey,
   $consumerSecret,
   ""(*token key*),
   ""(*token secret*)],
  "Content"
  ]

$oauthTokenParams =
  Association@URLQueryDecode@$oauthTokenResponseString;

(* Authorize it using the ones own account info (only works for the user who registered the keys) *)

URLRead[
 HTTPRequest["https://www.khanacademy.org/api/auth2/authorize",
  <|
   "Method" -> "POST",
   "Body" ->
    {
     "oauth_token" -> $oauthTokenParams["oauth_token"],
     "identifier" -> $username,
     "password" -> $password}
   |>
  ],
 "Content"
 ]

(* Obtain the request token proper *)

$accessTokenResponseString =
  URLRead[
   HTTPRequest[
    OAuthSigning`Private`HMACSha1SignatureService[
     "http://www.khanacademy.org/api/auth2/access_token",
     "HMAC",
     "GET",
     $consumerKey,
     $consumerSecret,
     $oauthTokenParams["oauth_token"],
     $oauthTokenParams["oauth_token_secret"]
     ]
    ],
   "Content"
   ];

$acessTokenParams =
  Association@URLQueryDecode@$accessTokenResponseString;

(* Make authenticated calls *)

Import[
 HTTPRequest[
  OAuthSigning`Private`HMACSha1SignatureService[
   callEndpoint,
   "HMAC", "GET",
   $consumerKey, $consumerSecret,
   $accessTokenParams["oauth_token"],
   $accessTokenParams["oauth_token_secret"]
   ]
  ],
 "RawJSON"
 ]

Update: now with docs*

Here are some docs for the ServiceConnections, OAuthClient, KeyClient, and OAuthSigning packages.

*Docs is a perhaps too strong a word for what they really are. Mostly they're stubs and things to try.


Mathematica has an oauth client but it's buried and undocumented. I tend to use it through the ServiceConnect framework but obviously my experience there will not help you as that's a) overkill for what you want to do and b) using oauth 2.

On the other hand there is oauth 1 support and some spelunking found me: OAuthSigning`Private`HMACSha1SignatureService (sorry about how mediocre that doc page is).

It takes an argument pattern like this per the docs:

OAuthSigning`Private`HMACSha1SignatureService[
 url,
 signatureMethod,
 httpVerb,
 consumerKey, consumerSecret,
 tokenKey, tokenSecret
 ]

then subbing in some values from you + the Khan Academy API docs:

Needs["OAuthSigning`"]

OAuthSigning`Private`HMACSha1SignatureService[
 "https://www.khanacademy.org/api/auth2/request_token",
 "HMAC-SHA1",
 "POST",
 "7cPJxRmA5ybuaNQz",
 "secret",
 "t4632213944267176",
 "4tsVQH6L5n2TGm8R"
 ]

And... it crashes my kernel without fail.

Digging in the actual implementation file we find we really want to use "HMAC" not "HMAC-SHA1" and with that knowledge:

OAuthSigning`Private`HMACSha1SignatureService[
 "https://www.khanacademy.org/api/auth2/request_token",
 "HMAC",
 "POST",
 "7cPJxRmA5ybuaNQz",
 "secret",
 "t4632213944267176",
 "4tsVQH6L5n2TGm8R"
 ]

"https://www.khanacademy.org/api/auth2/request_token?oauth_consumer_\
key=7cPJxRmA5ybuaNQz&oauth_nonce=S994QECCq0o50QBDrj26&oauth_signature_\
method=HMAC-SHA1&oauth_timestamp=1500597534&oauth_token=\
t4632213944267176&oauth_version=1.0&oauth_signature=\
K4RxAhNuEHKn8aA3QMcnvTs1n9s%3D"

Voilà

There's your request URL.

The OAuthClient` package uses a special wrapper to hold the tokenKey and tokenSecret but it's probably easiest just to go straight to this unless you want to fill in all of the flow details.

$\endgroup$
  • $\begingroup$ Thank you. I use URLExecute[myTokenURL] I get an error... I'll update my post. $\endgroup$ – Matt Green Jul 21 '17 at 1:04
  • 1
    $\begingroup$ @MattGreen let's chat. It'll be quicker to figure out what's going on. $\endgroup$ – b3m2a1 Jul 21 '17 at 1:07
  • $\begingroup$ I made some progress. I posted in the chat @b3m2a1 $\endgroup$ – Matt Green Jul 22 '17 at 0:47
  • $\begingroup$ Fantastic work @b3m2a1 thanks for everything. $\endgroup$ – Matt Green Jul 22 '17 at 21:30
  • $\begingroup$ @b3m2a1, "Here are some docs for the ServiceConnections, OAuthClient, KeyClient, and OAuthSigning packages."... Links no longer work. $\endgroup$ – rhermans Jun 17 '18 at 21:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.