Invalid token ("headless" API connection)

130 views
Skip to first unread message

Sean M

unread,
Apr 25, 2014, 11:28:04 AM4/25/14
to valenc...@googlegroups.com
Hi all,

I've been working to get an automated script to connect to Valence so we can issue API calls.

I found and have been following Viktor's detailed response (thanks Viktor!) in this thread:


To be clear, I'm taking the "...or" route he mentions, by extracting the userID and userKey values from apitesttool.desire2learnvalence.com, and then doing createUserContext using those values (along with the D2L host name, port, etc --- the 6-parameter version of that function).

Unfortunately, the value being returned from createUserContext is very simple: "Invalid token".

I don't know if this is referring to the user token, or the application tokens. Also, I've double-checked both and they seem ok. Is there anything else that could be causing this error, given this code flow?

Any help would be greatly appreciated.

Sean M

unread,
Apr 28, 2014, 1:08:04 PM4/28/14
to valenc...@googlegroups.com
More info in case it helps... we're currently at D2L 10.1, and Valence 1.0 is what we're working with at the moment.

Desire2Learn Staff: Viktor

unread,
Apr 29, 2014, 11:08:14 AM4/29/14
to valenc...@googlegroups.com
You shouldn't be getting an "invalid token" back from createUserToken()... I take it that you mean you do get a context back from that method and when you use it to make an API call, the service returns a 403 "Invalid token" to you?

If your App were not known to the LMS (i.e. not an enabled App ID) you would get back a slightly different error. This tells me that either you're not making a valid App Sig in your eventual API call (which likely means your App Key is not right for your App ID), or the User ID/Key you're using is somehow not useful.

I don't know what your comfort level with Python is, but I would highly recommend that you step through the walk through on the Python client SDK docs page:


Even if you don't intend to use Python in the long term. I recommend this because you can go through this process interactively and step by step, and thus have more direct control over what's going on at each step in the process to see what's going on. With an enterprise app built out of C# or Java, you're down to standard debugging techniques to feel your way through the workflow -- which also work fine: I'm just pointing out that an interactive shell that lets you execute each step as you go along and inspect the results is perhaps beneficial.

To get started with the python SDK as in that walkthrough, you need:
- to have a Python install, and it helps to have the 'pip' package installed to do python module install
- to have the 'requests' python module installed (which pip should find automagically)
- to have the 'd2lvalence' python module installed (which pip should find automagically)

Hope this helps.

Sean M

unread,
Apr 29, 2014, 2:27:12 PM4/29/14
to valenc...@googlegroups.com
Hi Viktor,

Thanks much for your help. 

>You shouldn't be getting an "invalid token" back from createUserToken()...
> I take it that you mean you do get a context back from that method and
> when you use it to make an API call, the service returns a 403 "Invalid
> token" to you?

Right, sorry. I did mean that I'm getting "invalid token" back from the actual API call, not createUserContext.

I went to look at the Python auth page you referenced, but I'm confused. That page advises me to go to the browser and catch the redirect URL after getting the result back from createUrlForAuthentication (using the php format for the function).

Do we need to issue a CURL call and catch the redirect every time we try to authenticate via script-only, then?

I'll get my thick head around this someday. Thanks again.

Desire2Learn Staff: Viktor

unread,
Apr 29, 2014, 3:18:55 PM4/29/14
to valenc...@googlegroups.com

On Tuesday, 29 April 2014 14:27:12 UTC-4, Sean M wrote:
Hi Viktor,

Thanks much for your help. 

No problem!
 
>You shouldn't be getting an "invalid token" back from createUserToken()...
> I take it that you mean you do get a context back from that method and
> when you use it to make an API call, the service returns a 403 "Invalid
> token" to you?

Right, sorry. I did mean that I'm getting "invalid token" back from the actual API call, not createUserContext.

OK, that makes sense.
 
I went to look at the Python auth page you referenced, but I'm confused. That page advises me to go to the browser and catch the redirect URL after getting the result back from createUrlForAuthentication (using the php format for the function).

So, the way the model works at a high level is as a three-legged auth model:

- You have a real user with a browser

- You have a third-party service or application that the user can visit (typically) that wants to make Valence API calls on behalf of that user

- You have the back-end LMS


When the real user visits the 3rd party app, before it can make API calls, it needs to ask the LMS for a User ID/Key pair to go with its App ID/Key pair so it can make API calls on behalf of the user. To do this, it redirects the user's browser to the LMS' "login path" to ensure that the LMS "knows" who the user/browser is:

- If the user's browser already has a web session withe LMS, then the LMS can say "I know who the user is" and send the right User ID/Key pair to the 3rd party app.

- If the user's browser doesn't yet have a session, then the LMS can seek to authenticate the user using the standard way for that LMS; THEN it can send the right User ID/Key pair back to the 3rd party app.

(Note that, in both cases, the User ID/Key pair gets generated specifically for THAT user and THAT app: it's not transferable to any other App ID/Key pair.)


This three-legged auth system makes good sense in the case where you really do have three separate parties. In cases like yours, where you want to write a (headless) IT service app that has no real "currently operating user with their web browser", we typically recommend that you create a service account -- a special purpose, "user" and "role" that only gets used for your service application.

So, in this case, you need to have a way to go through the above auth process every single time you need a valid User ID/Key pair for this service "user". There are a bunch of ways to do that, but they all involve having (at some point) a real user with a real browser stand up and play the part of that third leg in the auth process.

Typically what you do is either use a locally hosted version of one of our simple samples, or our online "API Test Tool" (https://apitesttool.desire2learnvalence.com/), or an interactive Python session with our Python SDK (as in the auth walkthrough I pointed to). Typically, for best security, we'd recommend that you do this process inside a secure environment, so the API Test Tool is perhaps not the best solution, especially if you're not going to connect with HTTPS, because it's sitting out on the internet. However, if you use HTTPS to connect it to your LMS, it's probably sufficiently safe.


For the process of "gathering a User ID/Key pair" you need to use your OWN App ID/Key credentials (that is, the pair you have received for the app/service you are writing).

If you're doing it interactively, you create an app context with those credentials. If you're using a sample, or the online tool, you provide your own App ID/Key pair as the ones to use (not the default ones in the form: those are a single set of public keys for demo purposes). You have to provide the host name of your LMS, and the port to call on. Then you go through the "auth process":

- If you're using an interactive session with the Python SDK, you create an "authentication URL" that you'll cut and paste into your browser... in order to do this, you also need to provide a "callback URL" telling the LMS where to send the User  ID/Key -- we suggest localhost because that will go back to the computer where your browser is, and likely your browser will complain it can't find the URL, but you can then copy the completed URL out of the address bar.

- If you're using the sample or online tool, the "callback URL" is hidden to you -- the form provides it automatically: the User ID/Key pair will get sent back to the web server running the sample/online tool.


When passing back the User ID/Key pair, the LMS will put them as query parameters onto that "callback URL" you provided in the request you just sent.

- If you're using the online tool or a sample, the tool/sample should automagically decompose this callback URL, peel out the credentials and show them to you.

- If you're using an interactive Python session, the LMS will redirect to "localhost" and tack on the User ID/Key pair in query parameters. Copy the entire URL from the browser's address bar, and feed that back into your Python session to create the "user context" -- this will peel the User ID/Key out of that result URI, and create a user context for you that you can use to interactively make API calls... you can also inspect it to see what the User ID/Key pair are, save them to disk, and so forth.


In any case, you have no successfully "gathered a User ID/Key pair" for YOUR service account to go with YOUR App ID/Key. You can cache that in a safe place accessible to your admin script/app (like in a configuration file). We highly recommend that you treat your app's App ID/Key pair and all User ID/Keys that go with it as highly sensitive information -- with those tokens ANYONE can make a call to your LMS pretending to be your App as a valid user, so you wan to put them in a safe place visible only to a small number of people and to the script/app that needs to use them.


Whenever the password for this service account changes you will need to go through this process again to gather a new User ID/Key pair. If your LMS has API tokens set to expire in a fixed time, then you'll also need to re-do this process when that time elapses. LMSes by default have this expiry time set to "indefinite" and the User ID/Key tokens expire only when the user's password changes, or the site admin denies access to third party apps for that user.

For another point of explanation, you can see this topic on our developer blog: <http://devs.valence.desire2learn.com/2012/04/10/valence-auth-howto/> It's several year's old, but the material still holds true, and the diagrams are different to the ones that appear in our online docs, so that contrast can help get a rounder picture.


I hope this clarifies the picture for you.

Sean M

unread,
Apr 29, 2014, 4:26:48 PM4/29/14
to valenc...@googlegroups.com
Ah-ha!  Your answer made me return to my code's plumbing and re-check everything, and I found that I had somehow run with the appID for the wrong server.  Changed that, re-did the user auth, and voilà, it's all good.

Thanks again for the great help Viktor! I've marked this as complete/fixed.

Sean M

unread,
Apr 29, 2014, 4:28:38 PM4/29/14
to valenc...@googlegroups.com
...or maybe not. I clicked a "Completed" button but that hasn't marked it on the thread list. Anyway, consider this complete.

Desire2Learn Staff: Viktor

unread,
Apr 30, 2014, 1:59:05 PM4/30/14
to valenc...@googlegroups.com
Cool -- glad to hear!
Reply all
Reply to author
Forward
0 new messages