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.
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.
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.
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.
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.
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.
Once that custompostMessagehas 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.
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!
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
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)
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.
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 firstname.lastname@example.org / canvas-docker.
Step 2 - Create a new ASP.NET MVC Core 2.2 application
Start Visual Studio 2019 and select Create a new project.
Select ASP.NET Core Web Application.
Set the 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.
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.
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.
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.
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.
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.
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.
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:
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.
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).
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.
After which they are the taken back to the web app having been authenticated.
Note that in this containerized instance of Canvas the default admin user has 'email@example.com' set as their name which is why an email address is being shown. This would normally be their proper name in Canvas.
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.
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.
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
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.
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.
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).
API access not required
Can provision users on-the-fly as they launch the tool
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
Configure an LTI tool in Canvas
Launch the tool
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
If a match is confirmed (and the signature matches), let the user access their information in your application
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.
This approach requires an LTI integration to be configured. It also must use OAuth2 to obtain instructor and student API tokens.
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
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 launches the tool from course navigation or elsewhere
Tool kicks off OAuth2 if it needs to obtain or refresh a token
Tool consumes the custom_canvas_api_domain and custom_canvas_course_id from the LTI launch and checks the Enrollments APIfor 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
Tool can then create/update/delete users from their database based on the results
Student launches the tool from the course
Tool kicks off OAuth2 if it needs to obtain or refresh a token
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.
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
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
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.
Tool polls the report to check progress
When the report is complete, tool downloads the report and parses the data to synchronize users
Tool can then create/update/delete users from their database based on the results
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.
This won’t be discussed here. For more information about integrating with Canvas as a SIS provider, please contact: Chase Pendleton <firstname.lastname@example.org> (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.
Can provision an entire course on-the-fly when an instructor or admin launches a tool from the course
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
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
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!
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.
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!
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
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.
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.
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.
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.
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.
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.
(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.
This approach requires an LTI integration to be configured with at least a course_navigation placement. It also must use OAuth2to obtain teacher and student tokens.
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
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 launches the tool from course navigation or elsewhere and assigns the available work to students, or the tool can do this automatically
Tool kicks off OAuth2 if it needs to obtain or refresh a teacher token
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?
Ok, hopefully you successfully produced the Sucess!! message from Part 1.
I thought getting the redirect wired up as far as the Success message was a good starting point, but there are a few details that I glossed over for the sake of simplicity, which I'll cover in this post.
I am not sure who the audience is going to be, and will try to explain things as clearly as I can. If the walk through seems too basic, or too detailed, I apologize. I'm happy to make adjustments based on feedback.
To follow along with this post, download the associated source code from this git branch:
If you're using Sql Server, there is a folder in the Visual Studio solution "SqlServer" containing scripts to create associated tables. The database I'm using is named [oauth2Test]. The scripts were created with Sql Server 2008, and tested on Sql Server 2014, Community Edition is more than adequate. If you're using a database other than Sql Server, you should be able to pull the schema from the scripts.
You will see a few places where I have implemented NLog. The logging files should be stored here: c:/logs/oauth2Test, as defined in the NLog.config file. Add your own logging where needed to enhance the output; this can help to follow the sequence of event.
Amendment to Part 1
In Part 1 I mentioned the Developer Key and storing values in the web.config. I forgot to mention the "Key" variable:
This is a required value in order for you to be able to request the user access key. In the source code associated with this post the value will be stored in the web.config <appSettings> variable [oauth2ClientKey]
We need to keep track of user state
If you look at the documenation for Step 1 of the workflow (OAuth2 - Redirect users to request Canvas access), specifically the more detailed definition of GET login/oauth2/auth, there is a reference to the variable state. Although this is an optional parameter, it is very useful. Particularly if you are running multiple web servers behind a load balancer where the user session state is unique from one server to the next.
Another important detail to note is that the LTI launch request happens only once. That means you receive the LTI launch parameters only when the user launches the app from Canvas. Once the app is launched it's running on your server, not the Canvas server, and you will not receive any additional launch parameters. To illustrate this:
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).
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.
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:
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:
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:
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.
The documentation is straight forward here, the required parameters are clearly defined. One detail to note is that the API call operates in two ways:
generate a new access token
refresh an existing access token
We'll focus first on generating a new access token. If you follow along with the comments in the code, it should be clear where each of the parameters comes from:
grant_type - this is defined by logic in our code, for now the value will be "authorization_code"
client_id - this is the value stored in the web.config parameter oauth2ClientId, which was created in the Developer Key
client_secret - this is the value stored in the web.config parameter oauth2ClientKey, which was created in the Developer Key
redirect_uri - this value must match the "URI" that was defined when the Developer Key was created, and in this example should match the Request.Url, i.e. it should match the URL that Canvas called to our oauth2Response method
code - is a parameter sent to us by Canvas in their redirect to our oauth2Response method
refresh_token - in this example does not exist yet, and should be NULL
The comments in the code should clearly show the origin of each value and how the values are stored and referenced.
Assuming the API call is successful, the results are stored in another helper object: userAccessToken.cs
For illustration purposes, the values will be returned to the client for display.
Important Note: You never want to return the access token or refresh toke, or the LTI parameters, back to the client. I am displaying them only for the purposes of demonstration.
You should be able to confirm the access token by inspecting the "Settings" of your profile in Canvas. If the token is successfully created, you will see a new entry under "Approved Integrations":
Launch the app again, refresh your "Settings" in Canvas, and you will see a second entry.
It's worth mentioning here that this situation is why the refresh_token is available. If you continue to generate a new access token every time the user launches your app, the list of access tokens in the user account will become extensive. I'll cover the refresh process later.
Caching the User Access Token
In order to be able to reuse the token next time the user launches the app, we need to store it somewhere. This is where we need persistent storage, to store the access token potentially for the life of the app or for the life of the user.
Note: The user can invalidate the token at any time by deleting the entry in their profile "Settings" mentioned above.
For access token caching there is a second table in the database associated with this example: accessTokenCache
The simple helper class sqlHelper also has methods to help manage records for this table. If you inspect code for the class userAccessToken you will see that the constructor that accepts the json string from Canvas makes a call to store the token in the cache table:
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.
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.
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 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:
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:
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:
There are three <appSettings> variables that we will use for this first run at the OAuth2 workflow.
"oauth2ClientId" - this is the value created in the Developer Key (screen shot above)
"consumerKey" - this is the consuer key you assigned during the LTI install of your application
"consumerSecret" - this is the shared secret you assigned during the LTI install of your application
Store these three variables in the web.config, see the associated source code for a reference.
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.
Using Visual Studio to view the source code you will find a single controller: HomeController.cs
If you browse to the default URL you will see the home page (https://your.domain.com) There is nothing fancy, basically the default home page that was generated by the MVC template with modified content and links. You can use this page to verify your web server is setup properly.
There are two additional methods associated with this example:
The OAauth2 Request Method: oauth2Request()
This is the method defined in the LTI XML above, the method that Canvas will use to execute the launch request. The LTI launch request must be a POST. If you would like to test this method you can use Postman to debug this method manually by making a POST calll to https://your.domain.com/home/oauth2Request
I have included helper classes that will handle LTI parameters received (oauthHelper.cs and ltiLaundhParams.cs), more importantly it will parse the LTI launch parameters and validate the OAuth signature. Step through the code if you would like to learn more about LTI launch parameters. A manual call from Postman will fail validation and return the result.cshtml view with an associated message.
With the debugger running, launch the app from Canvas and confirm that LTI validation is successful. Validation of the OAuth signature depends on the "consuerKey" and "consumerSecret" that you stored in the web.config. Looking at the constructor of oauthHelper.cs you will see where the "consuerSecret" is read from the config file and the OAuth signature is verified. Once the LTI parameters pass validation the response will be a redirect back to Canvas. In the browser you should see a prompt from Canvas asking the user to authorize access on behalf of the user.
By clicking "Authorize" you are prompting Canvas to redirect back to the application server, your web server, at the URL you defined when you created the Developer Key earlier: https://your.domain.com/home/oauth2Response
The OAuth2 Response Method: oauth2Response()
This is the method that Canvas will call if the user clicks the "Authorize" button, shown in the screen shot above. In the source code associated with this example, the web server will return a simple Success!! message to verify that the loop has been closed. Get this much of the application working and you have Step 1 working, and part of Step 2.
Create a Developer Key, pointing the redirect URL to your "oauth2Response" method
Install your application in Canvas, directing the LTI launch URL to the "oauth2Request" method.
Make sure your web.config has the three required parameters:
Launch the app from Canvas, confirm that the OAuth signature passes validation and you receive the "Authorize" option in the browser
Click "Authorize" and confirm you receive the "Sucess!!" response in the browser
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:
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:
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
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:
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?
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.
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:
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.
Index() - which we configured to receive a launch request from the Account navigation menu
Welcome() - which we configured to receive a launch request from the Course navigation menu
You will need to validate the signature in any method where you are receiving a launch request from Canvas.
NOTE: Keep in mind, once Canvas has launched your app you will not recieve any more data from Canvas, your application runs on your server. Something to think about and I'll cover some details to think about as we continue on.
It is also important to note that I am passing in the Consumer Secret. This is the private key that is shared between you and Canvas, and is used to generate the hashed signature string. You need to be able to look this value up based on where the request is coming from. Again, if you have multiple institutions accessing your tool I strongly recommend having a unique Consumer Secret for each institution.
Perhaps the best reason to use a trusted library is that you are able to focus on your business logic, you are able to focus on the functions of your app, and not spend time rewriting well established logic.
So, validating the signature can be as simple as a single line of code. Hurray!
But, you will learn a lot by writing your own if you have the time.
It should be easy to test your validation now:
Enter a different Consumer Key and Secret in Canvas vs. your app and make sure validation fails
Try sending the same nonce, how are you handling that?
Try sending an earlier timestamp, how are you handling that?
Try modifying other values sent in the Form params, does your signature still validate?
What does the user see if their request is rejected?
It is really up to you to flush out your test cases.
Getting past OAuth has been a topic of discussion in several places, I hope this helps.
There are several considerations once you get past authentication, I'll mention some of them in another post.
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.
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:
ID: <generated guid>
Description: Demo app to illustrate how to launch an LTI application in Canvas
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:
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:
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:
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:
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: