cancel
Showing results for 
Search instead for 
Did you mean: 

Developers Group

Instructure
Instructure

This blog post was authored by Xander Moffatt who is a Software Engineer on our Interoperability Team.

tl;dr
Safari blocks all 3rd-party cookies by default, which breaks LTI tools that rely on setting cookies when launched in an iframe. Instead, it exposes a new API for getting user-permitted access to set cookies from an iframe, which works though requires jumping through some fun hoops.

Background

Safari has been working towards this step for a few years now, since the introduction of the Storage Access API and Intelligent Tracking Prevention. These features were introduced to limit cross-site tracking that users never agreed to, and to preserve their privacy. These limitations have also grown more strict over the years, with most third-party cookies already being blocked by the time of the newest release, 13.1.

Before this release, third-party cookies were allowed to be set once the domain has set a first-party cookie, which occurs when the tool is launched in the parent browser window instead of a child iframe. Canvas implemented this change to launch the tool in the parent window, let it set a cookie, and provide a redirect url so that the tool is launched again in an iframe, with the ability to set third-party cookies.

This release makes Safari the first mainstream browser to fully block third-party cookies by default, though Chrome aims to ship the same features by 2022. The Storage Access API should also be made standard, providing a known way for LTI tools to still be functional. Note that this behavior can be turned off, by disabling the Preferences > Privacy > Prevent cross-site tracking checkbox.

When this behavior is enabled, the current behavior of an LTI tool launch in Canvas is to get stuck in an infinite loop, since Storage Access hasn’t been granted and so the cookie can never be set.

Storage Access API

There are only two methods in the Storage Access API, but they are more complex than they look. document.hasStorageAccess asynchronously resolves to a boolean indicating whether the iframe already has access to set its own cookies. In practice, this is almost never true until a call to the next method, document.requestStorageAccess. This method also asynchronously resolves to a boolean indicating whether the iframe now has access. The hoops that require jumping through come with this method.

  • this method must be called upon a user gesture (like tap/click). this means the user must click a button and the listener for this button must directly call requestStorageAccess. any calls that aren’t inside a listener will immediately return false.
  • this method won’t return true for a domain in an iframe unless the user has interacted with the domain in a first-party context. This is to make sure that the user knows and trusts this domain. Interaction in this case means another user gesture like a tap or click.
  • this method will return true if the user has had storage access granted in the last 24 hours.
  • once this has been called from a user gesture, the user has interacted in a first-party context, and then the request is sent again from a third-party context, then Safari will prompt the user using a browser dialog box to allow storage access. Once the user clicks Allow, then this method will return true and the tool can finally set the cookies it needs to authenticate its user.

Solution

There are a couple of ways to approach this situation from a Canvas-launching-LTI tools standpoint, and both of them are on the tool side, as opposed to the Canvas side. Canvas continues its behavior of providing a redirect url when the tool requests a full window launch, but the tool has some decisions to make.


If your LTI tool can handle being stateless and not setting cookies (ie it doesn’t require logging in, or the login process is fast so can be done on every launch), do it. Move any non-login cookies to window.ENV or something, let the user login if needed, and just plan on that whole flow happening on every launch.


If your LTI tool requires storing state in cookies and keeping the user logged in, there is a slightly more complex process to work with an in-line Canvas launch. Note that the Storage Access API happens in Javascript, but most LTI tools want to set httpOnly cookies from the server for sensitive cookies like a login token, so once the tool has Storage Access, a final redirect back to the server to set cookies and render the UI will be needed.

  1. When the tool launches, use document.hasStorageAccess to check if the tool already has Storage Access. This will most likely never be true, but if it is, redirect to the tool server to set cookies and render the UI.
  2. Request Storage Access using a user button click that calls document.requestStorageAccess. If the user has granted Storage Access within the last 24 hours, this will be granted. If granted, redirect to the tool server to set cookies and render the UI.
  3. If the request fails, then it’s time to get user interaction in a first-party context. Send a postMessage to Canvas requesting a full window launch, providing the tool’s normal launch url.
  4. Once that custom postMessage has been sent, Canvas will launch the tool again, in a full window. Canvas will send a platform_redirect_url in the request parameters, which is how you can tell it’s a full window launch. Get user interaction by having them click a button, and on that click redirect to the url Canvas supplied.
  5. Canvas will redirect to that url, which means another tool launch in an iframe. The tool will go through steps 1 and 2 again, and this time Safari should prompt the user to grant access. Once that happens, the tool has Storage Access and should redirect to the tool server to set cookies and render the UI.

Efforts are being made to encapsulate this behavior in some sort of gem/module, but since it touches both server- and client-side code it might be hard.

Though this method requires anywhere from 1-3 user button clicks before the app loads, it does provide a non-hacky way of interacting with cookies in Safari.

note that these are snippets that don’t have all variables and dependencies added. They are just for reference!
  • checking for storage access
document.addEventListener("DOMContentLoaded", () => {
if (document.hasStorageAccess) {
document
.hasStorageAccess()
.then((hasStorageAccess) => {
if (hasStorageAccess) {
redirectToSetCookies();
}
})
.catch((err) => console.error(err));
} else {
redirectToSetCookies();
}

ReactDOM.render(
<RequestStorageAccess />,
document.body.appendChild(document.createElement("div"))
);
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
  • requesting storage access
const requestStorageAccess = () => {
document
.requestStorageAccess()
.then(() => redirectToSetCookies())
.catch(() => requestFullWindowLaunch());
};

const buttonText = "Continue to LTI Tool";
const promptText =
"Safari requires your interaction with this tool inside Canvas to keep you logged in.\n" +
"A dialog may appear asking you to allow this tool to use cookies while browsing Canvas.\n" +
"For the best experience, click Allow.";

return (
<InteractionPrompt
action={requestStorageAccess}
buttonText={buttonText}
promptText={promptText}
size="medium"
/>
);‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
  • requesting a full window launch
const requestFullWindowLaunch = () => {
window.parent.postMessage(
{
messageType: "requestFullWindowLaunch",
data: FULL_WINDOW_LAUNCH_URL,
},
"*"
);
};‍‍‍‍‍‍‍‍‍
  • interact with user in a first-party context
const SafariLaunch = () => {
const redirect = () => {
window.location.replace(PLATFORM_REDIRECT_URL);
};
const buttonText = "Continue to LTI Tool";
const promptText =
"Safari requires your interaction with this tool outside of Canvas before continuing.";

return (
<InteractionPrompt
action={redirect}
buttonText={buttonText}
promptText={promptText}
/>
);
};

document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(
<SafariLaunch />,
document.body.appendChild(document.createElement("div"))
);
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
  • handle different types of launches, including full window, Safari, and non-Safari
# Safari launch: Full-window launch, solely for first-party user interaction.
# Redirect to Canvas for inline relaunch.
if safari_redirect_required?
@platform_redirect_url = params[:platform_redirect_url]
return render('safari/full_window_launch')
end

if browser.safari?
# Safari launch: request Storage Access, then redirect to
# :relaunch_after_storage_access_request with pertinent cookie info
# If Storage Access request fails, request a full window launch instead.
@id_token = id_token
@state = state
return render('safari/request_storage_access')
end

# Non-Safari launch: set cookies and render app launch
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Resources

https://webkit.org/blog/7675/intelligent-tracking-prevention/
https://webkit.org/blog/8124/introducing-storage-access-api/
https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/

more
6 1 670
Community Member

Hello Smiley Happy  

I have started to design ready-made canvas design templates for courses. This project I have started as an Open-Source code under MIT (which means free). and anyone can use this. I would love to hear your feedback/suggestions.

The cool thing about this project is Zero Dependency - (No need to include any and CSS or js files into your canvas instance)

 

My Github Project: Click Here - CanvasLMSDesigns

Don't forget to check the demo Smiley Happy 

Demo

Features

  • Zero Dependency - (No need to include any and CSS or js files into your canvas instance)
  • Compatible with Canvas LMS editor

How to use

  • Go to this file - Design-1/index.html - Click here
  • Copy index.html HTML codes
  • Paste into the Canvas LMS editor

 

 

This is my first design

more
3 0 949
Community Member

We've been working for a while on leveraging the Canvas API to work with other systems for particular learning use cases. We're developing a middleware app using ASP.NET Core MVC to manage the integrations.

We've been using the access tokens that each Canvas user can generate to work with the API. This is fine for development and testing but when we need to extend usage we want to avoid requesting users create their own tokens. A neater solution is to authenticate directly into Canvas using OAuth and, from this, get a token for the logged in user that can be used for subsequent API calls. This maintains the context based security that is a key feature of the access token.

Before I get into the steps to to getting OAuth to work in ASP.NET Core MVC and the intricacies of connecting to Canvas I'll give you a link to a GitHub repo that contains a very simple example. This is not production code and is an example only.

I also want to acknowledge the series of posts by garth@academicplatforms.com‌ on the OAuth workflow in .NET. I wouldn't be writing this now if it wasn't for Garth. I also got a lot of help from this post by Jerrie Pelser that works through an example of using OAuth2 to authenticate an ASP.NET ....

Getting Started

In this example I'm using a local instance of Canvas running as a Docker container. If you want to follow along then install Docker Desktop. Then download and run lbjay's canvas-docker container. This container is designed for testing LTIs and other integrations locally and comes with default developer keys:

  • developer key: test_developer_key
  • access token: canvas-docker

You can also log in to the Canvas instance and add your own developer keys if you want to.

Other thing that you'll need to started is an IDE of your choice. I'll be using Visual Studio 2019 Community edition but you could use Visual Studio Code or another tool that you prefer.

Step 1 - Make sure that the test version of Canvas is running

Start Docker Desktop and load the canvas-docker container. Once it has initialised it is available at http://localhost:3000/ 

The admin user/pass login is canvas@example.edu / canvas-docker.

Step 2 - Create a new ASP.NET MVC Core 2.2 application

Start Visual Studio 2019 and select Create a new project.

Visual Studio Start Screen

Select ASP.NET Core Web Application.

Visual Studio Project type screen

Set the Project name.

Visual Studio Project Name

In this case we're using an MVC application so set the type to Web Application (Model-View-Controller). Make sure that ASP.NET Core 2.2 is selected and use No Authentication as we're going to use Canvas.

Visual Studio project sub type

Step 3 - Let's write some code

 OAuth requires a shared client id and secret that exists in Canvas and can be used by an external app seeking authentication. The canvas-docker container has a developer key already in it but you can add your own. 

The default key credentials are:

Client Id: 10000000000001

Client Secret: test_developer_key

 

You can get to the developer keys by logging in to your local instance of Canvas and going to Admin > Site Admin > Developer Keys.

Now we need to store these credentials in our web app. For this example we'll put them in the appsettings.json file. You can see the code that we've added in the image below. Please note that in proper development and production instances these credentials should be stored elsewhere. Best practice for doing this is described here: Safe storage of app secrets during development in ASP.NET Core.

app settings json

In this case Canvas is the name of the authentication scheme that we are using.

Now the configuration for OAuth2 happens mostly in the startup.cs file. This class runs when the app is first initialised. Within this class is public void method called ConfigureServices in which we can add various services to the application through dependency injection. The highlighted zone in the image below shows how to add an authentication service and configure it to use OAuth.

Startup config

The basic process is to use services.AddAuthentication and then set a series of options. Firstly we set the options to make sure the DefaultAuthenticationScheme is set to use Cookies and the DefaultSigninScheme is also set to use cookies. We set the DefaultChallengeScheme to use the Canvas settings from the appsettings.json file.

We can chain onto that a call to AddCookie(). And then chain onto that the actual OAuth settings. As you can see we set "Canvas" as the schema and then set options. The options for ClientId and ClientSecret are self explanatory. The CallBackPath option needs to set to be the same as that in the Redirect URI in the key settings in Canvas. You may need to edit the settings in Canvas so they match. The image below shows where this is located.

Callback URI

The three end points are obviously critical. The AuthorizationEndpoint and the TokenEndpoint are described in the Canvas documentation. The Authorization enpoint is a GET request to login/oauth2/auth. As you can see, there are various parameters that can be passed in but we don't really need any of these in this case.

The Token endpoint is a POST request to login/oauth2/token. Again, there are various parameters that can be passed in but we don't really need any here.

The UserInformationEndpoint was the hardest endpoint to work out. It is not explicitly mentioned in the documentation. There is a mention in the OAuth overview to setting scope=/auth/userinfo. I couldn't get that to work but I may have been overlooking something simple. In the end it became apparent that we would need an endpoint that returned some user information in JSON format. There is an API call that does just that: /api/v1/users/self 

The AuthorizationEndpoint and the TokenEndpoint are handled automatically by the OAuth service in the web app. The UserInformationEndpoint is called explicitly in the OnCreatingTicket event. But before we get there we need to make sure that we SaveTokens and Map a JSON Key to something that we'll eventually get back when we call the UserInformationEndpoint.  Here we are mapping the user id and name Canvas.

 

That brings us on to the Events. There are several events that can be coded against including an OnRemoteFailure event. For simplicity's sake we've just used the OnCreatingTicket event which, as it's name suggests, occurs when Canvas has created a ticket and sent it back. 

In this event we set a new HttpRequestMessage variable to call the UserInformationEndpoint with a GET request. We need to add Headers to the request. The first tells the request to expect a JSON object. The second is the Access Token that Canvas has sent back to the web app for this user.

All that is left to do set a response variable to get the values back from Canvas for user information, we call the EnsureSuccessStatusCode to make sure we got a good response back, parse the JSON with user info and then run RunClaimActions to allocate name and id into the web app's authentication.

There is one other thing that we need to do on the startup.cs class. There is a public void Configure method in which we tell the app to use various tools and resources. In this file we need to add app.UseAuthentication() to tell the app to use Authentication. This call should come before the app.UseMVC() call.

Use Authentication

So, now the app is set up to use OAuth with Canvas. We just need a situation to invoke it and show the outcome.

To do this we will create a LogIn action in a new Controller. So create a new Controller class in the Controllers folder and call it AccountController.cs. In this controller we will add a LogIn Action.

Account controller

This Action will be called when the browser makes a get request to the Account/Login path. It returns a Challenge response which effectively kicks off the process of going to Canvas and authenticating which is what we just configured in startup.cs.

To call this Action I've added a link to the Shared/_Layout.cshtml file so that it appears on every page.

Login link

This basically renders as a link to the Login Action of the Account controller.

Now to see whether the user has successfully logged in and what their name is I've modified the Home/Index.cshtml file as follows: 

Index page with log in details

If the user is logged out the page will say "Not logged in". If the user is logged in the page will say "Logged in XXXX" where XXXX is the user's name in Canvas.

Step 4 - Test

Now when we run the application we get a plain looking standard web page but it does have a Log in with Canvas link and a statement saying we are not currently logged in.

Testing the integration

When we click the Log In with Canvas link we get sent to the Canvas Log in page (assuming we are not already logged in to Canvas). 

Testing the integration - Canvas login

The user is then asked to agree to authorize the calling web app. Note that the name, icon and other details are all configurable within the associated Canvas Developer key.

Authenticate

After which they are the taken back to the web app having been authenticated. Completion

Note that in this containerized instance of Canvas the default admin user has 'canvas@example.edu' set as their name which is why an email address is being shown. This would normally be their proper name in Canvas.

Summing up

If you are an ASP.NET Core developer looking to use OAuth with Canvas then this will, hopefully, have provided a starting point for you to get your own integrations working. It was a bit of struggle at times but half of that was returning to ASP.NET after some time away so there's been a fair bit of relearning done as well as quite a bit of new learning. I'm sure there are a heap of improvements that can be made. I'd love to hear suggestions.

more
3 2 12.7K
Adventurer

During 2019 I have been trying to use Canvas to help support the degree project process (for students, faculty, and administrators). One of the latest parts of this effort has been to look at some of the administrative decisions and actions that occur at the start of the process. A document about this can be found at https://github.com/gqmaguirejr/E-learning/blob/master/First-step-in-pictures-20190524.docx (a PDF is attached). The code can be found in SinatraTest21.rb at https://github.com/gqmaguirejr/E-learning. This code makes use of user custom data in conjunction with a dynamic survey (realized via an external LTI tool) and the administrative decision and action part of the process utilizes custom columns in the gradebook and automatically creating sections and adding a given student to the relevant section.

The Ruby code in LTI tool uses a token to access the Canvas API from the LTI tool to put values into the custom columns in the gradebook - this is probably not the best approach, but worked for the purpose of this prototype.

more
2 1 101
Adventurer

In conjunction with experiments concerning using an LTI interface to realize a dynamic quiz, I built a version of Canvas that could run in a Virtual Machine (specifically an image that could be run by VirtualBox).

Advantages of this approach include:

  • easy to try things without any risk to the real Canvas infrastructures
  • easy to give an OVA image of the VM to students

The image was built using an ubuntu version of linux by following the Quick Start instructions at https://github.com/instructure/canvas-lms/wiki/Quick-Start.

Details of the operation of the different containers that comprise this system will be described in a forthcoming Bachelor's thesis.

I should note that when I do the docker-compose to bring up the Canvas instance, it takes a very long time.

In order to do some experiments with the dynamic quiz, I created some fake users and enrolled them in a course. Additionally, since one of the things that I would like the dynamic quiz to exploit is knowledge of what program of study these users are in I augments the user's custom data with details of what program they are in.

The initial version of the program (create-fake-users-in-course.py) can be found at  https://github.com/gqmaguirejr/Canvas-tools .

The result is a course with a set of fake users as can be seen in the list of user's in this course:

List of users in the course

An example of fake program data is:

custom data for user Ann FakeStudent is {'data': {'programs': [{'code': 'CINTE', 'name': 'Degree Programme in Information and Communication Technology', 'start': 2016}]}}

Some further details about the above can be found at Creating fake users and enrolling them into a course: Chip sandbox 

more
1 0 621
Instructure
Instructure

Overview

One of the most common questions I'm asked when consulting our Canvas partners is how to synchronize the roster from Canvas into an external application. There are a variety of ways this can be accomplished, each with their advantages and disadvantages depending on the needs of your product and customers. This post is meant to be a resource for external tool providers hoping to maintain a synchronized roster between their systems and Canvas.

LTI-only Workflow

Requirements

This approach requires an LTI integration to be configured and visible somewhere within a Canvas course. Ideally, this LTI connection will already have an LTI SSO mechanism. If username, login ID, email, and/or SIS ID is required, make sure the privacy level is set to Public in the tool configuration. Otherwise, Canvas will only send an opaque LTI user id (as the user_id parameter) and a Canvas ID (as the custom_canvas_user_id).

Advantages

  • API access not required
  • Interoperable
  • Can provision users on-the-fly as they launch the tool

Limitations/Challenges

  • LTI tool is only aware of users who've launched their tool at least once
  • Unidirectional: cannot push new enrollments to Canvas
  • Cannot determine if users drop courses or are deleted from Canvas

Instructor/Admin/Student Workflow

  1. Configure an LTI tool in Canvas
  2. Launch the tool
  3. Tool consumes user information (name, email, ID's, roles, contextual information etc...) and attempts to match on an ID. Best practice is to match on the user_id from the launch and then fall back to some other ID if a match is not found
  4. If a match is confirmed (and the signature matches), let the user access their information in your application
  5. If no match is found, either or send them through a user-creation flow within the iframe, or auto-create a user for them based on the information in Canvas (you may want to let them set a password at this point, or email them a registration URL)

LTI + API Workflow

The main limitation of the LTI-only workflow is that it requires users to launch the tool through the LMS. This often does not make sense. For example, some tools are used only by instructors, so there is no opportunity for the students to launch an LTI tool. The LTI + API workflow works best for tools targeting teachers or students, but is limited when synchronizing rosters for large accounts.

Requirements

This approach requires an LTI integration to be configured. It also must use OAuth2 to obtain instructor and student API tokens.

Advantages

  • Can provision entire courses or sections with a teacher token, or entire accounts with an admin token
  • Can remove/hide users that have dropped courses or have been deleted
  • Bi-directional: if authorizing user has permissions, can create/update/delete enrollments within Canvas

Limitations/Challenges

  • Requires implementation of OAuth2
  • Requires heavy usage of Canvas-specific API’s making it not interoperable
  • If attempting to sync entire accounts, can be slow for large accounts due to API throttling

Admin Workflow

  1. Admin launches the tool from account navigation or elsewhere
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool retrieves a list of courses via the accounts API, then uses the Enrollments API for each course
  4. Tool can then create/update/delete users from their database based on the results


NOTE: This approach is not recommended for large accounts due to API throttling. Instead, either use the LTI + SIS + API approach in the next section or see the Instructor Workflow below.

Instructor Workflow

  1. Instructor launches the tool from course navigation or elsewhere
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool consumes the custom_canvas_api_domain and custom_canvas_course_id from the LTI launch and checks the Enrollments API for the course. Alternatively, the tool can check the enrollments for the instructor to sync all courses they are enrolled in; this requires consuming the custom_canvas_user_id
  4. Tool can then create/update/delete users from their database based on the results

Student Workflow

  1. Student launches the tool from the course
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool consumes the custom_canvas_course_id from the LTI launch and checks the Enrollments API for the course


NOTE: Some students may not have permission to view the list of users in their course.

LTI + Reports API Workflow

The main limitation of the LTI + API workflow is that it doesn’t work well for large accounts. By using the Reports API, tools can generate reports that list all users in an account. This is best used for institutions who’ve implemented SIS, but it’s not a requirement.

Advantages

  • Can provision entire accounts without worry of being throttled
  • Can remove/hide users who have dropped courses or have been deleted
  • Bi-directional: if authorizing user has permissions, can create/update/delete enrollments within Canvas via SIS Imports API

Limitations/Challenges

  • Requires an admin-level token
  • Best if institution has implemented SIS; SIS exports only show resources with SIS ID's. If SIS ID's are absent, you'll need to understand how to interpret the provisioning report
  • Requires implementation of OAuth2
  • Requires heavy usage of Canvas-specific API’s making it not interoperable
  • Requires parsing CSV's and downloading files
  • Requires understanding the structure of SIS CSV files or the provisioning report

Admin Workflow

  1. Admin launches the tool from account navigation
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a token
  3. Tool starts a report via API


NOTE: There are two reports of interest here. The easiest to interpret is the SIS Export report; however, the SIS Export report only shows users with SIS ID's. In this scenario, you’ll need to rely on the provisioning report instead.

  1. Tool polls the report to check progress
  2. When the report is complete, tool downloads the report and parses the data to synchronize users
  3. Tool can then create/update/delete users from their database based on the results

Instructor/Student Workflow

Instructors and students don’t have access to the reports API. However, the same functionality of the LTI + API integration described in previous sections can still be used to compliment the LTI + Reports API workflow. For example, you may want to do a nightly sync at the account level, but still use API + LTI to synchronize data when a user launches the tool.

NOTE: Admins can also manually send reports to a tool provider for one-time provisioning of new customers; however, in order to maintain an accurate list of users and enrollments, the tool would still need to sync data via LTI and/or API.

NOTE: The workflows which leverage API can be further automated or simplified by obtaining an admin token with the “become other users” permission. This token can then be used to make API requests on behalf of teachers and students by appending as_user_id=<student/teacher user id>. However, this can be problematic for high numbers of assignments or students as you’ll likely experience throttling issues.

SIS-only Integrations

This won’t be discussed here. For more information about integrating with Canvas as a SIS provider, please contact: Chase Pendleton <cpendleton@instructure.com> (SIS Partnerships Manager)

Roadmap: LTI Names and Role Provisioning Services (Membership Service)

We’re currently working toward implementation of the IMS LTI Names and Role Provisioning Service. We hope to have most, if not all, of the service implemented by the end of 2018. This service will make the LTI-only workflow more useful for instructors in particular as it doesn’t require all users in a course to launch the tool, and it'll allow tools to be aware of changes in the course roster.

Requirements

The requirements are the same as the LTI-only workflow outlined above, but you must also conform to the IMS LTI Names and Role provisioning Service as a tool provider.

Advantages

  • Does not require API access
  • Can provision an entire course on-the-fly when an instructor or admin launches a tool from the course
  • Interoperable

Limitations/Challenges

  • Will not be able to synchronize an entire account
  • Launch must occur in the context of a course
  • Unidirectional: cannot push new enrollments to Canvas

Workflow

  • User launches tool from course
  • Canvas sends a service URL that a tool can use to see a full list of users for the course
  • Tool can then create/update/delete users from their database based on the results

Concluding Remarks

This isn’t a comprehensive guide to solving the challenge of synchronizing users and enrollments between systems. I encourage creativity and product discovery with your customers to see how they envision your product working within Canvas. My hope is that this guide has at least allowed you to gain a better understanding of what is and isn’t possible so your organization can make decisions that help our mutual users have the greatest experience possible.

 

For those who’ve already solved this problem, how has your integration combined API and LTI to provision users from Canvas? Are there any interesting approaches you've taken that I haven’t considered here? Please share in the comments!


Jesse Poulos
Partner Integrations Specialist
Instructure

more
14 3 4,282
Surveyor

As Canvas admins we have a need to track external tool usage, across all course shells. We often are very interested to know adoption of a tool at our institution. Or if we determine that a particular tool has a technical problem, we need to be able to find which teachers to contact. Unfortunately neither the LTI Tool Report nor External Tool API are suited for this task, in themselves. Both are only effective at finding tool installations either at tool or account contexts. What is needed is a way to find when an external tool is enabled in a course navigation, used in an assignment or module. Fortunately, we can gather this information from the Assignments API, Modules API, and Tabs API and weave the external tool info with course and teacher info gathered from the Courses API.  

In this post, (and in perhaps subsequent posts) I will describe my approach, which is developed in Python. I am eager to receive input from others to determine how my code can be improved. My Python code uses a perhaps idiosyncratic style, choosing to wrap api calls in Python generators with closures, rather than using objects and classes. It seems that this more lightweight approach is well suited for getting data out of Canvas APIs and weaving it together into reports.  Again, I am interested in having a discussion on this - there may be a better way than what I am doing. Folks familiar with Clojure and the videos lectures of Richard Hickey will guess my motivations -  ultimately I  am trying to make something complex simple. 

The heart of the report building process looks like this:


# ----Iterate over all courses ---------------------------
for c in all_courses():
    # Build report in a Python default dictionary, where the default type is list,
    # allows appending without checking key first
    xtool_data_for_course = defaultdict(list)
    # ---- And for each course, retrieve all installed x-tools, tabs, modules, and assignments
    # ---------XTOOL INSTALLS-----------
    for x in course_xtools(c['id']):
        xtool_data_for_course['XTOOL_INSTALLS'].append(x)

    # ---------TABS--------------------
    for t in course_tabs(c['id']):
        xtool_data_for_course['TABS'].append(t)

    # ---------MODULES---------------------
    for m in course_modules(c['id']):
        for i in module_items(direct_url=m['items_url']):
            xtool_data_for_course['MODULES'].append(i)

    # ---------Assignments---------------------
    for a in course_assignments(c['id']):
        xtool_data_for_course['ASSIGNMENTS'].append(a)

Each of the for loops is iterating over a Python generator. The generator is "produced" by the api_wrapper_function, which "encloses" all the parameters specific to a particular endpoint (URL, authentication, and options) and typically receive an ID that specifies which course to query. The generator also may have a filter predicate specified, so only  active, external tools are returned. Another module specifies the defining parameters for the generators, so our client code here does not need to sweat the details.

The data-structure produced is a list of Python dictionaries for each course that is found to have at least one external tool in-place. 


[{COURSE_INFO:{course_code, name, id},
     INSTRUCTOR_INFO: [{instructor INFO}, {}..],
     XTOOL_INSTALLS:[ {xtool tool info}, {},...],
     TABS: [ {tab info} ... ],
     MODULES: [ {module item info}, ...],
     ASSIGNMENTS: [ {assignment xtool info}]

Each record for a course will have COURSE_INFO and INSTRUCTOR INFO and may have XTOOLS, TABS, MODULES, ASSIGNMENTS.  This data-structure is processed into a CSV report by another script. Right now the process works, with a few problems I am still working on. As you may guess, a process that inspects each and every course with multiple (four)  endpoint calls is going to be slow. May look at running multiple threaded agents that split up the list and then aggregate results at the end. Another challenge is surfacing the right information about external tools in course sites, and excluding less critical info. For example, my current report shows up with bunch of redirects. In most cases that is not what we are interested in. Ok, that is it for now. If folks are interested will the underlying functions/generators that make this work. Bit of code-clean up to do first! Would like to thank Peter Love for the advice he offered and help tracking down all the endpoints needed to generate this report. Finally do let me know if there are better ways of achieving this same ends. Let me down easy... but do let me know, thanks!

References:

Assignments API (Assignments - Canvas LMS REST API Documentation 

Modules API (Modules - Canvas LMS REST API Documentation ) 

Tabs API (Tabs - Canvas LMS REST API Documentation )

Python Generators - Generators - Python Wiki 

more
11 5 2,215
Community Member

Kansas State University is proud to announce our first open source LTI application. It is an attendance taking tool. The source code is available on GitHub.

Previously we released a Java library to interface with the Canvas API. (blog post | code) This API library is now being used by a couple other universities and we have received several pull requests to add more functionality. Thank you to the Canvas community for embracing open source software and contributing back! We hope that this will continue.

Now, as part of the attendance taking application, there are a few other bits we would like to highlight. All of our LTI applications are based on an LTI launch framework to handle common launch tasks and provide common resources. We hope that others may find it useful in implementing their own LTI applications. In order to assist in learning how to use it, we have created a minimal self-contained LTI application that shows how to use the launch framework. Here are some details on these parts:

Attendance LTI application

This application was developed before Instructure released the Roll Call application as open source so it may not be quite as valuable now but it may still be worth a look if there is functionality you are missing in the existing Canvas experience.

Initially, we developed this application for the K-State Polytechnic Campus in Salina, Kanas. Their aviation maintenance program has strict guidelines from the FAA, which requires them to track contact time between students and instructors down to the minute. Later, we extended the application to be more broadly useful to the campus community.

Some of the features of the application include:

  • Minute versus daily attendance tracking, configurable by the instructor on a per-course basis
  • Instructors can choose to allow or prohibit students from seeing details of their attendance records
  • Attendance can be pushed to the Canvas grade book as an assignment

There is still some room for improvement to make this application more useful to other institutions. Depending on your existing environment, here are some specific hurdles you may face when trying to deploy this application:

  • The application was written against an Oracle database. It should not take too much effort to make the application database agnostic since we use Hibernate. We may get around to this at some point to allow for better testing.
  • We tried to avoid using K-State specific terms and branding but some did slip in. For example our SIS ID is commonly called the "Wildcat ID" and shows up as "WID" in some parts of the application.
  • The deployment process is not exactly simple. It would be great to have a docker image that packages the application and container together for easy deployment. Currently it runs on Wildfly. There is an INSTALL.md file in the repository that details every step needed to install the application in a UNIX-like environment.

LTI Launch Framework

This framework is based on Spring and handles LTI launch requests. (source code: GitHub) Spring Security is used to handle verifying the OAuth signature that comes with the LTI launch request. It also assists your application in getting an API access token from the user via the OAuth2 flow.

Because this framework is based on Spring, you must implement some interfaces and make the implementations available as Spring beans. These beans handle persisting user OAuth tokens for subsequent application launches, generally setting up the LTI application, and ensuring that the application is communicating with the correct Canvas instance. The last part is vital for hosted Canvas customers because it prevents a test version of the application from accidentally interacting with a production instance of Canvas. This mixup can readily occur when the test environment gets automatically overwritten with production settings periodically.

We have run into challenges as it relates to error handling in Spring. At the moment it is left mostly up to your application to handle specific errors correctly. This leads to some code duplication for handling common LTI related error conditions. As time permits, we are working to improve this problem.

Minimal LTI application

We created a minimal LTI application to demonstrate how to use the LTI Launch Framework. (source code: GitHub) The application outputs a simple page of text. It only authenticates the LTI launch request and then echoes back information including the username and LTI parameters being used in the application.

Unlike the Attendance application, this one is trivial to deploy! It uses maven to build an executable JAR which contains an embedded Tomcat server. Running it as as simple as executing the command: java -jar lti-launch-example.jar

Comments (and pull requests) are welcome!

more
7 1 1,603
Community Member

Hey guys, 

I'll spare you my life story on this one, but I am the LMS Administrator at a non-profit institution. What that means is that I'm the administrator, the course designer, the data-analyst, the help team, and all things betwixt. I've had to learn about APIs from the ground up. Since most of the content in here is geared toward advanced users, I thought I would post something for the user who is just getting started. 

Step One - Get access token

Step One

The first thing you need when performing any API operation is an access token. You have to have administrative rights to do this. The good news is that you can auto-generate them yourself. Click on account - settings. Scroll down until you come to this and generate a new access token. 

Step Two

This step is the script itself. Python has a library available to make API requests. Naturally, this is called "requests". The documentation for this library can be found here.  There are several low priced books also available on Google-Play-Books. These books all assume some level of basic knowledge but if you want to further explore it, then they are good resources. Below is a picture of the script.

Python Code

The first part, URL, is the URL you are making your request to. The next and most important section is your headers. This is where you need your access token, or your request will never work. The part that is blurred it is where you will copy & paste your access token. 

After that, we use the variable r to form our request. This is where the library comes in handy. Here, we call our URL and use the headers to access it. Then we tell python to print our results. In this case, I have them printed in text and json format. The results are printed below. 

GET Result

The point of this is that it works. It returns your result with no hiccups. The next step of this project for me, personally, is to have these results saved to a CSV file that I can easily analyze. But the point of this is that it works. 

There are many useful things you can do with API calls, including POSTS, that allow you to create a course, delete a course, create and delete users, without ever having to access Canvas itself. 

more
13 4 2,986
Instructure
Instructure

Overview

 

As Instructure's Partner Integrations Specialist, I’m often asked how an LTI tool can return a grade to Canvas for an assessment that’s completed on the tool provider side. The answer varies based on a set of requirements the tool provider has. However, I’ve outlined the most common workflows I encounter to hopefully help others find the right place to start—keep in mind that a combination of these workflows can also be utilized, you don’t have to choose just one.

 

LTI-only Workflow

Requirements

 

This approach requires an LTI integration to be configured with an assignment_selection placement and a content item selection request. It should also be capable of executing the LTI Outcomes service.


Advantages

  • API access not required
  • Interoperable
  • Can return LTI Launch URL as submission, allowing the tool to iframe a submission on the submission details page and within Speedgrader without the user having to leave Canvas
  • Grades applied using the LTI Outcomes service will also mark the student as having completed the assignment; this isn’t the case when a grade is returned via Submissions API

 

Limitations

  • Teacher must manually configure each external tool assignment using content item selection
  • Student must launch each assignment so the tool can collect required information
    • As a result, a tool can’t have a series of assessments where grades are returned to separate assignments unless they provide the content as a common cartridge

 

Instructor Workflow

  1. Create an assignment in Canvas
  2. Choose “External Tool” as the Submission Type
  3. Choose the tool from the list that appears in the modal
  4. A properly configured tool allows the instructor to then choose a resource
  5. Tool returns a content item message to Canvas with the Launch URL to that resource
  6. Teacher saves and publishes the assignment


Student Workflow

  1. Launch the assignment triggering a launch to the launch URL provided by the tool when the teacher configured the tool
  2. Tool consumes the lis_outcome_service_url and lis_result_sourcedid for the student X assignment combination and stores it
  3. Student completes the assessment
  4. Tool either automatically grades the student, or waits for teacher to grade student
  5. Grade is returned to Canvas via LTI Outcomes service
  6. (optional) Using the LTI Outcomes service, the tool can also return a piece of plain text, a basic URL, or even an LTI Launch URL. This will be attached to a student submission object in Canvas, and it’ll be visible on the student submission page and in Speedgrader

 

LTI + API Approach

There are two flavors of this workflow. One uses the LTI Outcomes service for grading, while the other uses the Canvas submissions API.

Requirements

This approach requires an LTI integration to be configured with at least a course_navigation placement. It also must use OAuth2 to obtain teacher and student tokens.

 

Advantages

  • Allows a single launch point from Canvas (usually from course navigation) where a student can complete many assessments and have the tool provision assignments within Canvas on the fly and grade them as needed
  • Less manual work for the teacher
  • Organization of assessments can be offered either on the tool side, or by leveraging the Modules page in Canvas
  • Content-Item not required
  • Tools can create external tool assignments and use the API to simulate a student launching the tool in order to obtain data required for the LTI Outcomes service

 

Limitations

  • Requires OAuth2 to obtain either a Canvas API token for teachers (to grade) and students (to optionally submit)
  • Requires heavy usage of Canvas specific API’s, so is not interoperable
  • If grading is achieved via submission API, submissions are not marked complete after a grade is applied. The tool must also submit to the assignment using the submission API to mark complete. Furthermore, only plain text or URL’s can be provided

 

Instructor Workflow

  1. Instructor launches the tool from course navigation or elsewhere and assigns the available work to students, or the tool can do this automatically
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a teacher token
  3. Tool creates external tool assignments using the Assignments API. These assignments should have an LTI Launch URL that has query parameters for the assignment, or the path that launches directly to the assignment, e.g. https://some_launch.com/assignment/<tools_assignment_id>; or https://some_launch.com?assignment_id=<tools_assignment_id>;

 

Student Workflow

  1. Student launches the tool from the course navigation button
  2. Tool kicks off OAuth2 if it needs to obtain or refresh a student token
    1. Student picks the assignment within the tool and completes it
    2. Tool grades and submits the assessment to Canvas
      • Option 1: LTI for grading
        • Tool uses the sessionless launch API as the student to launch the tool
        • Tool consumes the lis_outcome_service_url and lis_result_sourcedid for the student X assignment combination and stores it
        • Tool uses the IMS Outcomes service to return a grade and an LTI Launch URL to their submission
      • Option 2: API for grading
        • Tool uses the Submissions API as the teacher to grade the student
        • (optional) Tool uses the Submissions API as the student to submit so the teacher can verify students completed the assessment

     

    NOTE: The process can be further automated/simplified by obtaining an admin token with the “become other users” permission. This token can then be used to make API requests on behalf of teachers and students by appending the as_user_id=<student/teacher user id>. However, this can be problematic for high numbers of assignments or students as you’ll likely experience throttling issues.

    I always like to hear about creative ways for integrations to grade and submit assignments. How has your integration combined API and LTI to grade and submit to Canvas?

    more
    11 0 4,239
    Surveyor II

    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:

    226478_pastedImage_2.png

    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":

    226480_pastedImage_6.png

    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:

    more
    4 4 2,060
    Surveyor II

    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:

    225693_pastedImage_4.png

    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

    225694_pastedImage_11.png

    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.


    225768_pastedImage_19.png

    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


    244405_devkey.png

    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.

    225789_pastedImage_1.png

    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:

    more
    6 8 5,618
    Surveyor II

    In .NET LTI Project - Part 1 - Connect to Canvas​ we got an app wired into Canvas, but it really doesn't do anything yet.  It was a simple exercise in how to configure Canvas to recognize your application.

    The next step is to understand the launch request.  When a user clicks on the link in Canvas to access your application, Canvas is going to send a request to your web server with a bunch of variables.  Let's take a look at those variables and what they mean.

    You might want to take tag this document from IMS Global as a point of reference:

    IMS Global Learning Tools Interoperability™ Basic LTIv1 Implementation Guide

    Inspect Your Launch Request

    If you're using the tutorial from Part 1, go to you HelloWorld controller and place a break point on the return view(); statement.  Attach to the w3wp.exe process to start debugging and click the ltiDemo link from the admin navigation panel.

    Once you have caught the break point, add the following watch variable for Request.Form.AllKeys

    form-keys.png

    Place break point on the return view(); statement in the Index() method, and compare the list of variables you receive there.  Basically you are comparing the difference between the data you receive when you launch from the admin navigation panel vs. the course navigation panel.

    You should see a difference in the variables associated with the course launch request vs. the admin launch request.  Many of the variables are the same, but there are differences.

    The standard LTI variables are defined in the IMS Global documentation here:

    IMS Global Learning Tools Interoperability™ Basic LTIv1 Implementation Guide

    Of great importance are the oauth_* variables, which are critical for authentication.  We'll get into these when we tackle the oauth piece of the puzzle.

    There are also "custom" Canvas variables, which you can research here:

    LTI Variable Substitutions - Canvas LMS REST API Documentation

    The Canvas API document uses "dot" notation instead of underscores.  For example, the Request.Form variable is "custom_canvas_api_domain", and in the API document it is "Canvas.api.domain".

    Take a look at both the IMS Global document and the Canvas API document and think about how each of these variables could be of use to you as you identify users, their roles, and the courses the parts of Canvas they are requesting from.

    • How can you use these variables to enforce different levels of user access in your app?
    • Which variables would you use to expose a set of features in your app to a student vs. an instructor?
    • How would you make sure you don't push an update to production from a launch request received from beta, or test?  Or the other way around?

    Conclusion

    Take a few minutes to launch your app from different locations in the Canvas UI, and note the differences in the variables that you receive.  Also take a look at the values.  This is also a good opportunity to think about how you want to parse these values out of the Request object for lookup in other parts of  your application.  A dictionary could be a useful way to allow for easy lookup of these variables later.  Or maybe you prefer a dynamic object for easy serialization to/from json.  How you store these variables is important as you start to think about how you are going to manage your session state.

    Food for thought:

    If a user has multiple tabs open and launches multiple instances of your LTI app, how are you going to keep track of which course they are launching from and make sure you don't mix course information?  Definitely something to start thinking about.

    Something like MemCached might be useful.

    If you do not use cloud services, I have used this product on my local machine in the past: NoSQL Database | Couchbase

    Ok, so we're getting to the point where we'll need to attack oauth.

    I'll try to work on a walk through that makes sense.

    Part 3 is published here:  .NET LTI Project - Part 3 - OAuth

    A basic Visual Studio project is published here:  LTI Demo Project​

    more
    8 1 5,467
    Surveyor II

    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.

    Conclusion

    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​

    more
    6 4 6,105
    Surveyor II

    During a session at InstructureCon 2016​ I realized that there are quite a few people who are interested in developing LTI tools for their institution, and looking for instruction on how to get started.  I thought maybe I could help, at least from a .NET perspective.

    I do realize that Canvas lives on the *nix platform, but .NET is fully capable of communicating with it.  The department I'm in currently uses the Microsoft stack (except for virtualization).  So, for those of you out there who are also using Microsoft technology I hope to make this easy.  Those who are not using Microsoft tech will hopefully be able to at least follow along.

    If you're diving into LTI I'm going to assume that you have a background in web application development in general, and will not get bogged down in setting up a development environment.  I will be happy to provide additional details later as needed, based on feedback.

    What you will need...

    I'll be working through this project using Visual Studio 2015, community edition should be fine.

    You will need a web server to host your demo application, along with an SSL certificate.  LTI will not work over standard HTTP, you will need to use HTTPS.  I'll be using IIS for this role.

    • Use Google to find an article on creating a self-signed certificate if you do not have an SSL certificate available.

    Common Starting Point

    As a recommendation, I am going to use the project from this tutorial to work through the steps of launching an LTI app in Canvas.

    I realize this is a simple tutorial, which is why I chose it for this task: I do not want to get bogged down in what the app actually does.  The focus here is really on how to wire up the LTI side of things, the app is almost irrelevant.

    Note:  If you do follow along using this tutorial, add a default value for the "Name" parameter when you add the "Welcome" controller.  Or, remove the parameter completely.  You'll see why later.

    Going through this process will give us a common point of reference.  For those who are more advanced, start from where ever you are comfortable, or whatever project you have in mind.  If you do nothing but implement the first three steps of the "Getting Started..." tutorial, you will have enough to launch your first LTI application in Canvas.  The key here is that through the process of creating the sample project, you have implemented methods capable of recognizing specific HTTP verbs, i.e. GET, PUT, POST, etc.  This is required in order to work with LTI.

    Launching your first LTI tool

    Step 1: Create the XML Application Definition

    So, let's see how we can launch our new LTI tool in Canvas (we'll get into OAuth and user validation later).

    The first thing we'll need is an XML file that defines our application, go to this URL to use a tool to help create your XML file:  XML Config Builder

    For this example, we'll add launch points for our LTI app to the Account navigation, and to the Courses navigation.

    Here is a description of how I completed the entry fields in the XML tool:

    Options

    • Name:  ltiDemo
    • ID: <generated guid>
    • Description: Demo app to illustrate how to launch an LTI application in Canvas
    • LTI Launch URL:  https://ltidemo.mydomain.com/
    • Icon URL:  <blank>
    • Custom Fields:  <none>
    • Launch Privacy:  Public
    • Domain:  ltidemo.mydomain.com
    • Extensions:  select "Course Navigation" and "Account Navigation"

    Course Navigation Settings

    Account Navigation Settings

    Click the "Generate XML" button, copy the results found under "XML Output", paste these results into an XML file in your project.  My file name is:  ltiDemo_config.xml

    Step 2: Push the XML Configuration to Canvas

    For the purposes of this simple demo, we will make the demo app available to the entire instance, i.e. all sub-accounts.

    • I strongly recommend using your test or beta instance of Canvas, log in to the instance of your choice
    • Click on Admin, and go to the Settings of your instance
    • Click on the "Apps" tab
    • Click on "View App Configurations"
      app-configuration.png
    • Click on "+ App"
      add-app.png
    • You should now have a pop-up view labeled "Add App"
      add-app-settings.png
      • Configuration Type:  select "Paste XML"
      • Name:  ltiDemo
      • Consumer Key:  ONE
      • Shared Secret:  TWO
      • XML Configuration:  copy your XML content and paste it into this entry field.
      • Click "Submit"

    NOTE:  In this case I have used a meaningless Consumer Key and Shared Secret, I'll discuss these values in more detail in a later document.

    Once you have completed these steps, under "External Apps" you should see "ltiDemo" listed.

    If you refresh your Canvas browser window, you should now see "ltiDemo" in your Account navigation.

    If you then browse into a course, you should see "ltiDemo" in your Course navigation.

    Step 3:  Let's Test !

    I'm going to assume that you are testing from your local development environment.  In this case, you will want to add an entry to your hosts file to handle local DNS lookup on your domain name.

    For Windows users:  c:/windows/system32/drivers/etc/hosts

    Edit this file and add the following line:

    127.0.0.1  ltidemo.<yourdomain>.com

    Save your change, then ping your domain and make sure you get a response.

    Now when you request an LTI launch from the browser on your development machine, the URL of your demo app will be found on your local machine.

    Refresh your Canvas browser page, and you should now see "ltiDemo" listed in your Account navigation options.

    Looking back at our XML configuration, if you click "ltiDemo" at the Account level, it should launch the following URL in an iFrame within Canvas:  https://ltidemo.mydomain.com/helloworld/welcome

    Now browse into any course, and you should see "ltiDemo" listed in the Course navigation options.

    Looking back at our XML configuration, if we click "ltiDemo" from a course it should launch the following URL in an iFrame within Canvas:   https://ltidemo.mydomain.com/helloworld/

    Conclusion

    Launching your own tools inside Canvas is really not too hard once you know how to do it.

    Keep in mind that we haven't addressed one ove the most favorite topics yet, which is OAuth.

    However the goal here was simply to demonstrate how the launch is wired up, and show how to display your tool in an iFrame within Canvas.

    I will begin working on Part 2, I hope this is helpful.

    Part 2 is published here:  .NET LTI Project - Part 2 - Launch Request

    A basic Visual Studio project has been published here: LTI Demo Project​

    more
    12 1 13.1K
    Surveyor II

    After attending a session at InstructureCon 2016​ (which was very well organized and well worth the trip), I thought it might be useful to post some helpful resources that I found while cutting my teeth on LTI.

    Canvas Dev and Friends

    This site has useful links to several resources, and is worth bookmarking:

    Canvas Dev & Friends - Instructure Tech Blog

    Specifically, there is a link to the IRC Channel, which is a live chat where you can ask questions to those who are on-line.  I was able to get validation of ideas and techniques I was using to get things working.

    Canvas Dev and Friends Modules

    There is also a link labeled "a module on lti" that will take you here:

    Canvas Dev and Friends

    If you want some hands on exercises to work through all the specs you have been pouring over, I strongly recommend working through this course.  There are two modules: one on LTI, and one on the API.

    It is worth working through both, you may very well pick up a few things you didn't know yet about the API.

    Postman - A Useful Tool for Testing

    If you haven't used this tool yet, you should consider getting familiar with it:

    Postman | Supercharge your API workflow

    This tool will allow you to make API calls manually and inspect the results, which will help you to visualize the data you are expecting to receive in your app.  The LTI and API docs are pretty good, but sometimes it helps to see it while you're trying to code it.

    I have also used Restlet/DHC, which does essentially the same thing:

    https://dhc.restlet.com/

    Either way, it is very useful to have a tool that allows you to test syntax and parameters without having to debug a bunch of code.

    LTI Common Cartridge XML Config

    In order to get your own LTI app working, you will need an XML configuration file.

    You can view a sample LTI configuration file here by clicking on the LTI Config link on this page:

    https://lti-tool-provider.herokuapp.com/

    This config file can easily be modified to get your app installed in Canvas, and is a good starting point for getting your LTI app installed in Canvas.  With this file you can easily play with all of the navigation options and user access parameters.

    EduAppCenter also provides a tool to help you build your own custom LTI configuration:

    XML Config Builder

    This is a user interface allowing you to enter your information and select options, and it will build your custom XML file for you, a great tool to help you get started.

    OAuth

    Your LTI app is not going to work without understanding OAuth 1.0

    Here is a tool that will walk you through the OAuth process:

    OAuth 1.0 Authentication Sandbox

    You can use values from this walk-through to test your OAuth methods, you should be able to generate the same output that the sandbox creates.  This makes it really easy to validate your output.

    NOTE:  I strongly recommend that you find an OAuth library, writing your own will be time consuming and likely frustrating.  There is no reason to re-invent the wheel, unless of course you enjoy the challenge.

    IMS Global - Sample Code

    IMS Global is the organization responsible for defining the specifications for LTI.  You can find all the documentation your eyes can consume at their website.  Here is a link to the LTI sections:

    Learning Tools Interoperability | IMS Global Learning Consortium

    These guys are also kind enough to provide sample code for several languages, which can help get you started:

    Learning Tools Interoperability: Sample Code | IMS Global Learning Consortium

    Summary

    I think I've hit the key tools that I used to get started with LTI, hopefully this will be useful to someone out there.

    The best thing you can do if you're banging your head is to complete the Canvas Dev and Friends  modules, it will help.

    I'm sure there are plenty more resources out there.  If anyone has additional resources they have found to be useful, please share!

    Update

    I have posted a three part tutorial on how to conenct an application to Canvas using LTI.

    The example uses .NET, but walks through the steps you need to take:

    more
    16 2 5,285
    Labels