Invalid Token (403 Forbidden) On GET Request

175 views
Skip to first unread message

Matt Mencel

unread,
Jul 28, 2014, 6:40:16 PM7/28/14
to valenc...@googlegroups.com
I'm writing a Ruby library for D2L integration.  It took a while, but I finally figured out all the pieces I needed to make it work and now I can call the "/d2l/api/lp/1.2/users/whoami" URI and it returns my "It Worked!" response from my Rest block plus the response JSON.

"It worked !"
"{\"Identifier\":\"47906\",\"FirstName\":\"API\",\"LastName\":\"User\",\"UniqueName\":\"api-user\",\"ProfileIdentifier\":\"JtVGn4cUKz\"}"

Awesome!  Except I can't do anything else.  I've tried several other actions, for example return all enrollments for a user with id 47906 using "/d2l/api/lp/1.2/enrollments/users/47906/orgUnits/".  This and all the other API URIs I've tried all return "Invalid token" 403 Forbidden.

This user is in an administrative role that should have privileges to do just about anything.  Am I missing a step somewhere?  Do I have to enable some feature to allow API calls for this role or something?

I'm thinking maybe that the code that is building the signature strings isn't correct.  I may have to dig into the PHP or Python libraries and compare the values that get created from those....with the values I'm getting from Ruby.  Hmmmm...

Thanks,
Matt


Matt Mencel

unread,
Jul 28, 2014, 11:48:59 PM7/28/14
to valenc...@googlegroups.com
OK...so I've made a few more adjustments and compared my Ruby output to both the Python and PHP library outputs.  I'm almost positive I'm creating the exact same URIs with Ruby.  If you want to see my test code you can peek at it in this Gist...


Thanks,
Matt

Desire2Learn Staff: Jacob Parker

unread,
Jul 29, 2014, 9:28:53 AM7/29/14
to valenc...@googlegroups.com
Cool!!!

I look at this today. Comparing the output against our other SDKs is a good strategy. Signatures have the useful (frustrating) property that they stop your app from using the APIs until it can get them right :)

Matt Mencel

unread,
Jul 29, 2014, 1:14:12 PM7/29/14
to valenc...@googlegroups.com
OK...the code works.  The documentation for this path is wrong...

 GET /d2l/api/lp/(version)/enrollments/users/(userId)/orgUnits/

orgUnits should NOT BE CAMEL CASED!!!  I'm assuming all paths should be downcased, because as soon as I changed orgUnits to orgunits in my test code, it worked perfectly.  I now downcase all paths in my method...

def create_authenticated_uri(path, http_method)
  parsed_url
= URI.parse(path.downcase)
  uri_scheme
= 'https'
  query_string
= get_query_string(parsed_url.path, http_method)
  uri
= uri_scheme + '://' + $hostname + parsed_url.path + query_string
  uri
<< '&' + parsed_url.query if parsed_url.query
 
return uri
end


...but I banged my head against that documentation error for hours.  Bad luck picking the one example in the docs that was never going to work.

Matt

Desire2Learn Staff: Viktor

unread,
Jul 31, 2014, 1:46:28 PM7/31/14
to valenc...@googlegroups.com
The documentation is not wrong here. The back-end implementation does in fact match against a route that includes the camel-case version of 'orgUnits'. I can speculate on what's going on here:

- The back-end service's route loader (the thing that matches the URL path against a controlling handler for the API call) may be case-insensitive when it's doing route matching, so that if your API call passes the auth check, then 'orgunits' and 'orgUnits' will both serve to match the route to the right handler.

- The auth signatures however, are almost certainly NOT case sensitive; additionally, (and because of this), I believe it's a requirement that the path-component that forms the middle part of the base-string is lower-cased before signature generation, so that both clients and back-end service can always assume that the lower-case form of the URL will be uniformly used to generate auth tokens.

- This means that, if you're not using one of our auth client libraries, the signature-generation code you write should lower-case the path component when forming the base string. Here's the Python code for example:

    def _build_tokens_for_path(self, path, method='GET'):
        if self.invalid_path_chars.search(path):
            raise ValueError("path contains invalid characters for URL path")
        time = self._get_time_string()
        bs_path = urllib.parse.unquote_plus(path.lower())
        base = '{0}&{1}&{2}'.format(method.upper(), bs_path, time)
        app_sig = self.signer.get_hash(self.app_key, base)
        if self.anonymous:
            user_sig = ''
        else:
            user_sig = self.signer.get_hash(self.user_key, base)
        # return dictionary containing the auth token parameters
        return {self.APP_ID: [self.app_id],
                self.APP_SIG: [app_sig],
                self.USER_ID: [self.user_id],
                self.USER_SIG: [user_sig],
                self.TIME: [time]}

It wasn't just that route that wouldn't work for you: any route that had upper-case characters in it will exhibit this problem if you don't lower-case your path when forming the base-string for signature generation.

I will make sure that the docs around authentication point out the necessity of this: I'm not sure the docs don't already do this, but admittedly, the ID Key Authentication topic in the docs is very technical and very dense.

--
Viktor
Reply all
Reply to author
Forward
0 new messages