In .NET LTI Project - Part 2 - Launch Request we looked at the variables that are received in the launch request. These variables are a vital part of user authentication using OAuth.
In very general terms, OAuth allows an authenticated user in Canvas to be identified and authenticated in your application. Canvas will send you the identity of the user in the launch request, and you will use OAuth to verify that the identity sent to you has not be tampered with. Once you have verified the integrity of the credentials you have received, you can masquerade as that user.
Consumer Key and Shared Secret
In the first article .NET LTI Project - Part 1 - Connect to Canvas we looked at how to connect your application to Canvas using the XML configuration. Part of that process included the Consumer Key and Consumer Secret. These two values now come into play.
In the launch request, Canvas will send you the Consumer Key which is a unique identifier. Think of it as a user id. If you are working with multiple institutions, or multiple applications, this would be the "user id" for each institution or application. For example, if you are creating multiple LTI tools for your institution, this would be the unique id for each tool that you are exposing. Or if you are supplying a tool to multiple institutions, this would be the unique id for each institution.
The Shared Secret is the password. Canvas will never send you the Shared Secret, it is meant to be private, and should only be known by you and Canvas. Canvas will use the Shared Secret to create an encrypted hash of the form parameters that are sent to you in the launch request as the oauth_signature.
When you receive the launch request, you will look at the variable oauth_consumer_key to lookup the shared secret on your end. You will then use the Shared Secret to validate the OAuth signature found in the launch request parameter oauth_signature.
Make sure you understand the importance of the values Consumer Key and Shared Secret, and think about how you are going to store and retrieve them in your app. What naming convention will you use to generate your Consumer Key and Shared Secret? Would something like a GUID make sense, at least for the Shared Secret?
More Detailed OAuth Information
Here is a site that talks about the OAuth variables, and the sequence of operations in general. This will give you some good information if you want to dig in and understand all of the steps involved:
As I wrote my own OAuth class, I ran into a wall and got stuck. As I tried to figure out what I was doing wrong, I found this library: DotNetOpenAuth
Note: I did have to make an update to the code to properly handl URL encoding. If you use this library, take the time to debug and understand the steps.
Having an example to illustrate is sometimes very helpful to get you over a hump, and this code helped me to find where I was missing an iteration of URL encoding. If nothing else, working through this code might help to understand the steps involved, and see them in action.
If you decide to write your own OAuth algorithm, I suggest referencing this tutorial to validate your results:
If you can reproduce results from a known set of variables, you are well on your way. This sandbox also discusses the basic requirements to generate the OAuth signature.
NOTE: Make sure you understand the security structure, and the role of the nonce and timestamp, and have a strategy to verify unique messages. This is definitely not a "security how-to".
Why Fight It? Find a library.
In the end I decided to use a library. If someone has already taken the time to create and debug a well established technology, could it be more stable than my own home grown version? I eventually found that IMS Global (the authority on LTI) has published sample code. At that point I put my own library to bed, and adopted the IMS code. They have sample code for multiple languages here:
Source code for this library can be found here:
Using this library, validating the OAuth signature is one line of code. For ease of readability, and to call out the namespace where you can find the method of interest, this is a bit long winded:
bool signatureVerified = (LtiLibrary.Core.OAuth.OAuthUtility.GenerateSignature(request.HttpMethod, request.Url, request.Form, consumerSecret) == request.Form["oauth_signature"]);
When you receive the launch request, that's when you want to validate the OAuth signature.
Calling GenerateSignature and passing in the variables will return a string. If that string does not match the string you received from Canvas in the oauth_signature variable, reject the request and do not allow the user to access your app.
In relation to the test project used in .NET LTI Project - Part 1 - Connect to Canvas you would use the line of code in your HelloWorld controller in the following methods:
- Index() - which we configured to receive a launch request from the Account navigation menu
- Welcome() - which we configured to receive a launch request from the Course navigation menu
You will need to validate the signature in any method where you are receiving a launch request from Canvas.
NOTE: Keep in mind, once Canvas has launched your app you will not recieve any more data from Canvas, your application runs on your server. Something to think about and I'll cover some details to think about as we continue on.
It is also important to note that I am passing in the Consumer Secret. This is the private key that is shared between you and Canvas, and is used to generate the hashed signature string. You need to be able to look this value up based on where the request is coming from. Again, if you have multiple institutions accessing your tool I strongly recommend having a unique Consumer Secret for each institution.
Perhaps the best reason to use a trusted library is that you are able to focus on your business logic, you are able to focus on the functions of your app, and not spend time rewriting well established logic.
So, validating the signature can be as simple as a single line of code. Hurray!
But, you will learn a lot by writing your own if you have the time.
It should be easy to test your validation now:
- Enter a different Consumer Key and Secret in Canvas vs. your app and make sure validation fails
- Try sending the same nonce, how are you handling that?
- Try sending an earlier timestamp, how are you handling that?
- Try modifying other values sent in the Form params, does your signature still validate?
- What does the user see if their request is rejected?
It is really up to you to flush out your test cases.
Getting past OAuth has been a topic of discussion in several places, I hope this helps.
There are several considerations once you get past authentication, I'll mention some of them in another post.
A basic Visual Studio project has been published here: LTI Demo Project