Skip navigation
All Places > Canvas Developers > Blog > 2017 > April
2017

The Challenge

While the Student View button is easily found under the Settings panel of the course navigation menu, many of the instructors at my institution still would prefer for it to be accessible on their home screen. Other Learning Management Systems offer a Student View button that is a bit more global, thereby allowing an instructor to quickly jump into the Student View from just about any location in LMS. Ian Heung had previously suggested the idea for this feature in his idea, Quick Toggle Student View, and its status has been updated to be on the Product Radar, but I am uncertain as to what the timeline for deployment may be.

 

Solution

I wanted to provide users with a solution for the interim period of time, while we wait for the release of this new feature. So I set out to create a javascript based option that would do the following:

  1. Determine if the user has an associated role as a Teacher for one or more courses.
  2. Render a "global" Student View button in the upper right corner the courses associated with this user.
  3. Hide the button when the user triggers the Student View.
  4. Prevent the button from rendering if the user is in a course where he or she is technically a Student.

 

Appended Global Student View Button Position

 

For this solution, I simply created a custom javascript file with script that is deployed whenever a course successfully loads. The script assesses and validates information associated with the user, and uses Instructure's stylesheets, along with some javascript and jQuery, to append and render the default student view button in a new location. This button is placed along the top header of a course in the upper right corner, and can be clicked at any time by the teacher to trigger the student view. 

 

Demo

 

Repository

If you are interested in applying this to your environment, please feel free to fork or download from my Bitbucket repository, which can be found here.

Ok, hopefully you successfully produced the Sucess!! message from Part 1.

I thought getting the redirect wired up as far as the Success message was a good starting point, but there are a few details that I glossed over for the sake of simplicity, which I'll cover in this post. 

 

I am not sure who the audience is going to be, and will try to explain things as clearly as I can.  If the walk through seems too basic, or too detailed, I apologize.  I'm happy to make adjustments based on feedback.

 

To follow along with this post, download the associated source code from this git branch:

 

If you're using Sql Server, there is a folder in the Visual Studio solution "SqlServer" containing scripts to create associated tables.  The database I'm using is named [oauth2Test].  The scripts were created with Sql Server 2008, and tested on Sql Server 2014, Community Edition is more than adequate.  If you're using a database other than Sql Server, you should be able to pull the schema from the scripts.

 

You will see a few places where I have implemented NLog.  The logging files should be stored here: c:/logs/oauth2Test, as defined in the NLog.config file.  Add your own logging where needed to enhance the output; this can help to follow the sequence of event.

 

Amendment to Part 1

In Part 1 I mentioned the Developer Key and storing values in the web.config.  I forgot to mention the "Key" variable:

This is a required value in order for you to be able to request the user access key.  In the source code associated with this post the value will be stored in the web.config <appSettings> variable [oauth2ClientKey]

 

We need to keep track of user state

If you look at the documenation for Step 1 of the workflow (OAuth2 - Redirect users to request Canvas access), specifically the more detailed definition of GET login/oauth2/auth, there is a reference to the variable state.  Although this is an optional parameter, it is very useful.  Particularly if you are running multiple web servers behind a load balancer where the user session state is unique from one server to the next.

 

Another important detail to note is that the LTI launch request happens only once.  That means you receive the LTI launch parameters only when the user launches the app from Canvas.  Once the app is launched it's running on your server, not the Canvas server, and you will not receive any additional launch parameters.  To illustrate this:

  1. Launch your test app from Canvas with the debugger running and a breakpoint in your oauth2Request, and take a look at the parameters you receive (i.e. the variables that the helper class parse out of the request).
  2. Place a breakpoint in the oauth2Response method, click the "Authorize" button in the browser and review the parameters you receive.  You will find no LTI parameters.  Even though the test app is clearly presented on the Canvas page, you now have no idea who the user is.  At this point your app is loaded and running on your web server and is not receiving any further parameters from Canvas.
  3. Right click on the iFrame where your "Success!!" message is displayed, and select "Reload frame", again with the breakpoint in the oauth2Response method.  Inspect the parameters you receive, there are no LTI parameters.

Point being, the [state] parameter allows us to create a unique identifier that we can use to keep track of our user.

 

When Canvas redirects back to the test app calling the oauth2Response method, it is essentially the same scenario.  Canvas is not making another LTI launch request.  It is entirely up to you to keep track of which request belongs to which user.  This is where persistent caching comes in to play (I mentioned a database at the end of Part 1), and the state variable plays an important role.

 

Caching User State

Back to the GET login/oauth2/auth call.  When you send a value for [state to Canvas, that value will come back to you in the parameters received in oauth2Response.  To quote the Canvas document OAuth2 - Step 2:

"If your application passed a state parameter in step 1, it will be returned here in step 2 so that your app can tie the request and response together."

 

We are going to "tie the request and response together".

The key here is to generate a unique value for state each time a user launches your app.  Looking at the source code associated with this post you will see that I have chosen to use a GUID.

  • Guid stateId = Guid.NewGuid();

The goal is to associate our LTI values with this unique identifier so we can refer back to the LTI values when we receive our response.  To achieve this we need to store this information somewhere.  There are options for state caching.  Memcached is a great option as it will time out and expire records automatically.  Databases are a great option for persistent caching.  In this case we need both expiring cache and persistent cache (discussed later).  For that reason I am going to keep it simple, and use a database.  

 

Take a look at the SqlServer folder in the Visual Studio solution for database scripts.  I'm using Sql Server, but you should be able to use the schemas to create tables in whatever database you chose to use.  For our user state there is a table named [stateCache], with a simple schema consisting of three fields:

  • stateId - this is the GUID, or unique identifier
  • stateJson - this is the string representation of the oauthHelper object
  • tstamp - a timestamp allowing you to determine the age of the record (it will be up to you to determine your strategy to expire the state cache)

 

I have included a simple helper class to manage database records:

  • sqlHelper.cs

 

Why am i creating a new state record for every LTI launch?

I'm not sure if this is a question that anyone is considering, but it might be worth mentioning.

Let's say UserA launches your tool from sitea.instructure.com, from their English class.

Then UserA launches your tool from siteb.instructure.com, from some other class.

If you reuse the same state, your streams will cross.  You need to capture the state for each specific launch request so you can be absolutely certain you are communicating with both the proper instance of Canvas, and the exact course or assignment the user launched from in the instance.

 

Changes to the oauth2Request method

The change here is minimal.  There is a new line of code to save our launch parameters (what I'm calling our "user state" or [state]).  You will see that I am now storing a new GUID in a variable, and serializing the oauthHelper object to a json string.  Using those two values I am storing our [state] for later reference:

  • sqlHelper.storeState(stateId, jsonState);

 

Changes to the oauth2Response method

There are quite a few changes to this method, and plenty of comments.

To illustrate the missing LTI launch parameters, the first line of code is parsing all parameters received, which you can inspect for clarification.  The code is documented to explain the purpose of each step, so I'll summarize here.  You will see where the unique state identifier that we sent to Canvas is returned to us, and used to query our [state] from the database:  

  • stateJson = sqlHelper.getStateJson(Guid.Parse(state));

Now we have successfully tied the OAuth2 request in Step 1 to the OAuth2 response in Step 2.

 

With the [state] retrieved from our database cache, and the parameters received from the Canvas response, we are ready to make an API call to generate a new user access token.  Take a look at the method requestUserAccessToken to inspect the mechanics of the API call and associated parameters.

 

Request the User Access Token

Requesting the access token consists of a POST call to Canvas: POST request to login/oauth2/token

The documentation is straight forward here, the required parameters are clearly defined.  One detail to note is that the API call operates in two ways:

  • generate a new access token
  • refresh an existing access token

We'll focus first on generating a new access token.  If you follow along with the comments in the code, it should be clear where each of the parameters comes from:

  • grant_type - this is defined by logic in our code, for now the value will be "authorization_code"
  • client_id - this is the value stored in the web.config parameter oauth2ClientId, which was created in the Developer Key
  • client_secret - this is the value stored in the web.config parameter oauth2ClientKey, which was created in the Developer Key
  • redirect_uri - this value must match the "URI" that was defined when the Developer Key was created, and in this example should match the Request.Url, i.e. it should match the URL that Canvas called to our oauth2Response method
  • code - is a parameter sent to us by Canvas in their redirect to our oauth2Response method
  • refresh_token - in this example does not exist yet, and should be NULL

The comments in the code should clearly show the origin of each value and how the values are stored and referenced.

 

Assuming the API call is successful, the results are stored in another helper object: userAccessToken.cs

For illustration purposes, the values will be returned to the client for display.  

 

Important Note:  You never want to return the access token or refresh toke, or the LTI parameters, back to the client.  I am displaying them only for the purposes of demonstration.

 

You should be able to confirm the access token by inspecting the "Settings" of your profile in Canvas.  If the token is successfully created, you will see a new entry under "Approved Integrations":

Launch the app again, refresh your "Settings" in Canvas, and you will see a second entry.

It's worth mentioning here that this situation is why the refresh_token is available.  If you continue to generate a new access token every time the user launches your app, the list of access tokens in the user account will become extensive.  I'll cover the refresh process later.

 

 

Caching the User Access Token

In order to be able to reuse the token next time the user launches the app, we need to store it somewhere.  This is where we need persistent storage, to store the access token potentially for the life of the app or for the life of the user.  

 

Note: The user can invalidate the token at any time by deleting the entry in their profile "Settings" mentioned above.

 

For access token caching there is a second table in the database associated with this example: accessTokenCache

The simple helper class sqlHelper also has methods to help manage records for this table.  If you inspect code for the class userAccessToken you will see that the constructor that accepts the json string from Canvas makes a call to store the token in the cache table:

  • sqlHelper.storeUserAccessToken(this);

With the access token stored, we will be able to reuse it later, which will allow us to take advantage of "refreshing" the token instead of always generating new tokens and overwhelming the user profile.  I'll cover refreshing the token in the next post.

 

Summary

This felt like a good place to stop for now, there is some new code here to play with and some details to ponder on.   Hopefully the logic so far is easy to follow with respect to the topics covered.  I have tried not to over complicate the source.

 

There are a few key details raised in this example:

  • Having a caching strategy is important.  You will need a strategy for tracking user session state (specifically the LTI launch parameters), and a strategy for keeping track of the user access tokens that are being generated.
  • Always generating new access tokens creates a great deal of clutter in the user profile, and continues to open holes in the user account.  The long term goal is to have at most one active user access token for each user at any given time.
  • The user can invalidate the access key at any time by deleting the record in their profile settings, the user maintains full control and can lock your application whenever they like.

 

I'll start working on the next post, to cover the token refresh.

Part three of this post can be found here:

I just came from a workshop on "Language and Accessibility" (as a follow-upp to a seminar that we had a month ago on the topic - the slides from the seminar are available (in Swedish) from https://www.kth.se/social/files/58c17d95f276545c4bc556c8/Spr%C3%A5k-och-tillg%C3%A4nglighet.pdf ). One of the issues that came up is providing users with dyslexia with material presented in the Dyslexie font ( Dyslexie Font: The dyslexia font which eases the reading  ). Has anyone looked at being able to set a particular CSS file to be used for the user's Canvas UI based upon a user's profile? I imagine this to be an extension of the fact that the user can choose their UI language (for example, as commonly used here English or Swedish, based upon their user settings (as stored in the user's 'locale' attribute' in the format described in RFC 5646)). One approach would be to encode this desire for a special font in a subtag for the language and another would be to actually add a custom CSS attribute to the user object. Has anyone done anything like this?

 

Having an attribute that specifies a custom CSS file would probably be cleaner but would require a new field in the object. An interesting side benefit of the having both the ability to specify a custom CSS and font would be that one could have multiple language pages (ala Using tabs for content in three languages: Chip sandbox ) where the user's choice of language would the automatically the selected visible alternative.

 

Note that in the above when referring to a "custom CSS file" I am thinking in terms of choosing one from a set of files for the site, as opposed to having each user able to upload their own CSS file. 

Recently I was asked to create a custom grading tool based on rubrics, which requires modifying student grade data.  I had created a LTI toolkit to run some reports and automate SIS integration workflows, in that scenario I was using a token generated in a service account.  But using a service account token to modify grades is not an accepted method.  For grading we want to be able to verify a few important details and take advantage of existing functionality within Canvas:

  • The user has access to the course (masquerading is technically possible, but prone to errors)
  • The user has privileges to modify grades
  • Leverage the existing "View Grading History" feature build into Canvas

 

Canvas documentation on the OAuth2 workflow can be found here: OAuth2 Overview documentation

 

The example used for this post assumes the application will be launched from within Canvas using LTI integration.  If you have not yet worked through OAuth and LTI, these articles can provide an overview of how to get started before you dive into OAuth2:

 

Part 2 and 3 of this example can be found here:

 

Before you get started...

To get through this example, you will need to have the following knowledge and tools

  • You have successfully integrated at least one custom application that you have written with Canvas using LTI, which also implies the following:
    • You have a development environment setup and ready to go (language of your choice)
    • Your environment includes a web server
    • You have configured your web server with an SSL certificate
  • Have access to a database to cache information
  • You have administrative access to a test instance of Canvas
  • You are familiar with making API calls
  • You are familiar with using Git code repositories

 

If you have experience with web apps, this is not complicated.  This walk through should be straight forward.

Source code for this walk through can be found here:

The branch of interest for this walk-through:

Getting Things Ready in Canavs

The first step towards OAuth2 is completing the LTI install of your application within your Canvas instance.  If you have not created an LTI application yet, I suggest completing the three LTI posts mentioned above, I will not cover those steps again here.  .NET LTI Project - Part 1 - Connect to Canvas covers steps for registering your application in Canvas.

 

For the simple application associated with this example, I generated the LTI XML using the tool found here:

Here is the result (substitute your.domain.com with the URL specific to your test environment):

<?xml version="1.0" encoding="UTF-8"?>
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
<blti:title>OAuth2 Test</blti:title>
<blti:description>Test app to prove out Canvas OAuth2 worflow</blti:description>
<blti:icon></blti:icon>
<blti:launch_url>https://your.domain.com/home/oauth2Request</blti:launch_url>
<blti:extensions platform="canvas.instructure.com">
<lticm:property name="tool_id">D48C2D1E-C3BC-443E-9BA3-BDF000FDC91B</lticm:property>
<lticm:property name="privacy_level">public</lticm:property>
<lticm:property name="domain">your.domain.com</lticm:property>
<lticm:options name="course_navigation">
<lticm:property name="url">https://your.domain.com/home/oauth2Request</lticm:property>
<lticm:property name="text">OAuth2 Test</lticm:property>
<lticm:property name="visibility">admins</lticm:property>
<lticm:property name="default">enabled</lticm:property>
<lticm:property name="enabled">true</lticm:property>
</lticm:options>
</blti:extensions>
<cartridge_bundle identifierref="BLTI001_Bundle"/>
<cartridge_icon identifierref="BLTI001_Icon"/>
</cartridge_basiclti_link>

 

Point of interest in the XML:

The XML configuration above should create a Course menu option labeled "Oauth2 Test".

When you install the application in Canvas, make a note of the consumer key and shared secret for reference later.

 

You will also need to generate a "Developer Key".  Follow the steps in this Canvas article:

For the purposes of the exercise, use the following redirect URL (with your domain):

  • https://your.domain.com/home/oauth2response
    • This is not the same URL defined in the XML

This method call (/home/oauth2response) will line up with the source code associated with this example.

The result of creating the developer key will generate a unique ID for your redirect:

 

 

Required Variables

There are three <appSettings> variables that we will use for this first run at the OAuth2 workflow.

  1. "oauth2ClientId" - this is the value created in the Developer Key (screen shot above)
  2. "consumerKey" - this is the consuer key you assigned during the LTI install of your application
  3. "consumerSecret" - this is the shared secret you assigned during the LTI install of your application

 

Store these three variables in the web.config, see the associated source code for a reference.

 

Code Walk-thru

Using the sample project as an aide, I'll walk you through the first part of the OAuth2 workflow which is very straight forward.  The sample project was created from the default ASP.NET MVC project template, and will use the default HomeController.

 

Note:  The sample project was created with as few steps as possible, it is a simple MVC application with a single controller.  This is not a recommended template for a production application, but only an attempt to provide a simple, clean easy to follow example.  After following along with this example, I strongly recommend thinking through the steps, and building your own solution.

 

 

Home Controller

Using Visual Studio to view the source code you will find a single controller: HomeController.cs

If you browse to the default URL you will see the home page  (https://your.domain.com)  There is nothing fancy, basically the default home page that was generated by the MVC template with modified content and links.  You can use this page to verify your web server is setup properly.

There are two additional methods associated with this example:

 

The OAauth2 Request Method: oauth2Request()

This is the method defined in the LTI XML above, the method that Canvas will use to execute the launch request.  The LTI launch request must be a POST.  If you would like to test this method you can use Postman to debug this method manually by making a POST calll to https://your.domain.com/home/oauth2Request

 

I have included helper classes that will handle LTI parameters received (oauthHelper.cs and ltiLaundhParams.cs), more importantly it will parse the LTI launch parameters and validate the OAuth signature.  Step through the code if you would like to learn more about LTI launch parameters.  A manual call from Postman will fail validation and return the result.cshtml view with an associated message.

 

With the debugger running, launch the app from Canvas and confirm that LTI validation is successful.  Validation of the OAuth signature depends on the "consuerKey" and "consumerSecret" that you stored in the web.config.  Looking at the constructor of oauthHelper.cs you will see where the "consuerSecret" is read from the config file and the OAuth signature is verified.  Once the LTI parameters pass validation the response will be a redirect back to Canvas.  In the browser you should see a prompt from Canvas asking the user to authorize access on behalf of the user.


 

By clicking "Authorize" you are prompting Canvas to redirect back to the application server, your web server, at the URL you defined when you created the Developer Key earlier:  https://your.domain.com/home/oauth2Response


 

The OAuth2 Response Method: oauth2Response()

This is the method that Canvas will call if the user clicks the "Authorize" button, shown in the screen shot above.  In the source code associated with this example, the web server will return a simple Success!! message to verify that the loop has been closed.  Get this much of the application working and you have Step 1 working, and part of Step 2.

 

 

Quick Overview

  1. Create a Developer Key, pointing the redirect URL to your "oauth2Response" method
  2. Install your application in Canvas, directing the LTI launch URL to the "oauth2Request" method.
  3. Make sure your web.config has the three required parameters:
    • oauth2ClientId
    • consumerKey
    • consumerSecret
  4. Launch the app from Canvas, confirm that the OAuth signature passes validation and you receive the "Authorize" option in the browser
  5. Click "Authorize" and confirm you receive the "Sucess!!" response in the browser

 

Summary

I felt this was a good place to start, enough to get things working without being too overwhelming.  Getting this far you have verified that your dev environment is working properly, and that Canvas is properly configured.  Next steps include requesting a user token and caching some values for reuse.  If you don't have a database available there are many options:

I have Sql Server in my dev environment and will be using that for examples, but any database will do.

Part 2 will be posted as soon as I can make the time to get it down on "paper".

 

I am happy to try and answer any questions that come up, and happy to edit this post based on feedback.

Hopefully this will be useful to someone out there.

 

Part 2 and 3 of this example can be found here: