Recipe for Making LTI® 1 Tool Providers
Overview
LTI (Learning Tools Interoperability®) provides a standard mechanism for authorizing users accessing a web-based application (Tool Provider) from another web-based application (Tool Consumer, typically an LMS). It can be seen as replacing a login page which a Tool Provider may otherwise have provided and avoids the need to distribute a username and password to each user. Instead a signed launch message is received from the Tool Consumer which can be verified and then trusted. This message should contain sufficient data from which to create user accounts and relevant resources (or resource mappings) "on-the-fly". Users gain a seamless experience without the need for any pre-provisioning, involvement of any other servers (for example, identity providers), or changing of any firewalls (message is sent through the user's browser). LTI works best when the Tool Provider delegates full responsibility for authorizing users to the Tool Consumer and does not allow users to directly access their system, thereby bypassing this authorization. This means that there is no need for the two systems to be synchronized with any changes to user privileges, so there is no risk of a user being given access to resources to which they are no longer entitled.
1EdTech provides numerous resources for enabling your application to use LTI. Once you complete the steps below, you should apply for 1EdTech certification. Certification testing is available to 1EdTech Members and will ensure that your products utilizes LTI correctly.
Ingredients
A connection between a Tool Consumer and a Tool Provider is achieved by the latter providing the following information:
- A URL where users can access the Tool Provider system
- A Consumer Key to uniquely identify the system from which users are coming
- A Shared Secret to secure the messages being sent between the two systems
All Tool Consumers providing LTI 1 support will be able to configure a connection to a Tool Provider using these 3 items. In addition, a Tool Consumer will normally offer options for:
- which parameters are passed on launch (e.g. whether, or not, to include personal data about users);
- where the Tool Provider page should be opened (typically in a new window/tab, in a frame within the Tool Consumer page, or within an iFrame which also retains the course navigation menu of the Tool Consumer);
- custom parameters which are passed on launch which can be used to configure the integration – these parameters may be for all launches to the tool, or specific to individual links.
Utensils
All the preparation and "cooking" occurs at the Tool Provider end, so they are free to choose:
- a storage medium, such as a database
- a scripting language, such as Java, PHP, Ruby
Method
A Tool Provider must implement the following elements in order to add LTI support to its system:
- An option to allow Tool Consumer configurations (key and secret) to be set up and managed
- A script to reside at the URL which can process incoming launch messages
1. Tool Consumer Configurations
An option must be added to the Tool Provider's management system to allow new Tool Consumers to be added and managed (edited and deleted). The minimum data required for each Tool Consumer is their key and secret. However, additional data is likely to be useful. For example, the open source LTI class libraries available for PHP1 and Java currently use a database table with the following fields for tool consumers:
- name – the name of the customer/system to which the record relates
- consumer_key – the consumer key
- secret – the shared secret
- lti_version – the version of LTI supported by this Tool Consumer
- consumer_name – the name of the Tool Consumer (as provided in its launch messages)
- consumer_version – the version of the Tool Consumer (as provided in its launch messages)
- consumer_guid – the GUID for the Tool Consumer (as provided in its launch messages)
- css_path – the URL of a CSS file (as provided by the Tool Consumer in its launch messages)
- protected – a flag to limit access to messages containing the same Tool Consumer GUID
- enabled – a flag to toggle the availability for this Tool Consumer
- enable_from – a date/time from which this Tool Consumer is entitled to access
- enable_until – a date/time until which this Tool Consumer is entitled to access
- last_access – the date on which the last request was received from this Tool Consumer
- created – the date/time when this Tool Consumer record was created
- updated – the date/time when this Tool Consumer record was last updated
There is no need to implement all of these fields but some may be found useful additions. For example, the name field provides a human-readable description of the Tool Consumer. (Only the consumer_key and secret fields are essential.)
The lti_version field may become useful if/when support for LTI 2 is added, but in the meantime is of little value.
The consumer_name and consumer_version fields can be useful if you want to know what sort of system a Tool Consumer is and, perhaps, modify your behavior based on this information (for example, to take advantage of a feature only supported by specific Tool Consumers).
The tool_consumer_guid and protected fields can be useful to ensure that a consumer key and shared secret are not used from more than Tool Consumer system, but note that, like the consumer_name and consumer_version fields, the tool_consumer_guid field is not supported by all Tool Consumers and can change over time (for example, when a system is upgraded).
The enabled, enable_from and enable_until fields can be very helpful in managing access to licensed resources. The enabled flag makes it very easy to turn off access from a Tool Consumer should this ever be necessary. The date/time fields allow access to be limited to a specific period, such as the duration of a license. But these fields depend upon whether there are related business processes to make use of them.
The last_access, created and updated fields are essentially for information purposes only. However, the last_access field can be helpful when trying to identify any Tool Consumers which are no longer active and could be expunged.
Consumer Keys and Shared Secrets
The consumer key should be unique to the Tool Consumer system; if a customer has more than one system (for example, development, staging and production) then each should be given a different key to avoid clashes in IDs (assuming each of these systems is connecting to the same Tool Provider system, but even then duplicate keys and secrets are best avoided).
A consumer key can be any unique string; it could, for example, be the domain name of the Tool Consumer system, or simply a GUID.
A shared secret is used to secure the messages sent between the systems, it should, therefore, be strong – a random string of at least 15 characters, some systems use a GUID for the secret.
User Interface
Once the fields to be used for capturing details of a Tool Consumer have been determined, web pages for creating, editing and deleting such records are needed. These can just be added to the existing Tool Provider system as additional options, with appropriate permissions to prevent unauthorized use.
2. Script for Incoming Launch Messages
An incoming launch message is simply an HTTP POST message of a normal web form with parameters passed as name and value pairs using the application/x-www-form-urlencoded
format. The parameters are named according to the LTI specification. It will have been signed using the process defined in the OAuth 1 specification. The script placed at the URL used to configure LTI connections in Tool Consumers should perform the following checks and tasks:2
- Is this an LTI launch request?
- Is this a valid LTI launch request?
- Are all the required parameters present?
- Establish a user session
- Redirect the user
1. Is this an LTI launch request?
Just because a message is received by the script does not mean it is actually an LTI launch request. So the first step is to check if the message received conforms with the requirements of an LTI launch request. The required characteristics of an LTI 1 launch message are as follows:
- It should be an HTTP POST message
- It should include a POST parameter named lti_message_type with a value of
basic-lti-launch-request
- It should include a POST parameter named lti_version with a value of
LTI-1p0
- It should include a POST parameter named oauth_consumer_key with a non-empty value
- It should include a POST parameter named resource_link_id with a non-empty value
There are very few required parameters for an LTI launch, but if any of these is missing it means that the message is not an LTI launch message, or has not been properly constructed, and so it should be rejected.
2. Is this a valid LTI launch request?
Having established in Step 1 that the message received represents an LTI launch request, the next step is to verify its authenticity. All LTI launch requests are signed using OAuth 1 to ensure that their contents cannot be altered en route to the Tool Provider - that is, to ensure that the data received by the Tool Provider is identical to the data which was sent by the Tool Consumer. At the same time a check can also be made to ensure that the message is not a duplicate of one which has already been received and processed.
The OAuth signing process inserts the following parameters into the data being passed in the message:
- oauth_callback – not used by LTI but should always have a value of
about:blank
- oauth_consumer_key – the unique key assigned to the Tool Consumer which can be used to look up their associated shared secret
- oauth_nonce – a unique value identifying the message which can be used to avoid duplicates being processed
- oauth_signature – the signature itself
- oauth_signature_method – the method used to generate the signature, LTI currently supports HMAC-SHA1
- oauth_timestamp – the time at which the message was signed which can be used to ensure it is a current request
- oauth_version – the version of OAuth used, this should always be
1.0
Most of the work involved in the checking of a signed message is handled by an OAuth library available for most programming languages. The library will merely need to be given access to the shared secret associated with the consumer key for the sender of the message, and a method to confirm that the nonce value is not a repeat. The tasks it carries out are:
- check that the timestamp is within a specified interval either side of the current server time (typically ±5 minutes)
- check that the nonce does not have the same value as that of another message received (this check can be limited to only those messages falling within the permitted time interval and from the same tool consumer)
- generate the signature for the message using the shared secret for the consumer key included in the message and compare the value with the one received
If the message passes all these checks then it can be asserted to have come from the stated Tool Consumer and the data received should be trusted.
3. Are all the required parameters present?
Just because the data passed in the message has come from a trusted source does not mean that all the data that a Tool Provider relies upon to make their service available to a user is present. The LTI specification has very few required parameters; most are either recommended or just optional (though the 1EdTech certification process does place additional requirements on tool consumers). This means that it is entirely possible for a valid message to be received which does not contain one or more parameters which are essential to a Tool Provider.
It is strongly recommended that Tool Providers make as few assumptions about the parameters received as possible; it is better to provide a customer with lower quality service than no service at all if some useful data is missing. But there will almost always be parameters which a Tool Provider needs before it can deliver a service to the user; for example, the user_id parameter is a common requirement so that you know which user is making the connection. If this parameter is not included in the launch message it is quite legitimate to refuse (in a user-friendly manner) to accept the request.
4. Establish a user session
Having received a valid message containing all the required data, the request may now be actioned. Typically this would entail:
- cancelling any existing session for this Tool Provider which may be currently defined in the user's browser
- checking if the user ID is already known:
- if not, provision a new account for them
- if so, update any of the details which have changed (e.g. name)
- checking if the resource link ID is already known:
- if not, provision any working space which may be required for new links
- if so, update any of the details which have changed (e.g. title)
- Initializing a login session for the user, capturing any data from the launch which may be required later, such as:
- user ID
- role
- resource ID
- return URL
The user session established as part of this process will often be given an ID which is passed with each request from the user's browser via a cookie or a query parameter; this is an implementation issue and is not constrained by the LTI specification. The main objective is to allow the Tool Provider to know who the user is whenever an HTTP request is received from the user's browser without needing to authenticate them again and verify their authorization to access the resource(s) being requested.
5. Redirect the user
The final step in processing an incoming LTI launch request is to redirect the user to the content page (e.g. resource or activity) which is the intended destination. The URL for the location of this page may depend upon a number of factors, such as:
- the ID of the resource link from which the launch originates
- whether this is the first launch from this resource link
- the role of the user
- the values of any custom parameters included in the launch message
If the request is to be rejected then it is best practice to return the user to the tool consumer with a user-friendly error message. If a return URL was not supplied by the tool consumer in the launch message, then a standalone error page should be used. It is best not to redirect a user to a page which contains a login form since LTI users are not expected to have credentials to login directly to a tool provider - this is the purpose of using LTI!
At the end of this step, the user will have been either redirected to the relevant content page in the tool provider, or been given an error message.
LTI Class Libraries
There are class libraries available for some languages (PHP and Java) which abstract away the LTI-specific code so that Tool Providers need only make calls to the API provided and need not understand LTI itself.[^3] For example, the 1EdTech PHP class library would be used to validate an incoming launch message as follows:
<?php
use IMSGlobal\LTI\ToolProvider;
require_once('vendor/autoload.php');
class ImsToolProvider extends ToolProvider\ToolProvider {
function onLaunch() {
// Insert code here to handle incoming connections - use the user
// and resourceLink properties of the class
// to access the current user and resource link.
}
}
// Cancel any existing session
session_start();
$_SESSION = array();
session_destroy();
session_start();
$db = new PDO(DB_NAME, DB_USERNAME, DB_PASSWORD); // Database constants not defined here
$data_connector = ToolProvider\DataConnector\DataConnector::getDataConnector(DB_TABLENAME_PREFIX, $db);
$tool = new ImsToolProvider($data_connector);
$tool->setParameterConstraint('user_id', true, 50);
$tool->setParameterConstraint('roles');
$tool->handleRequest();
?>
In this example, a declaration is made stating that a user ID parameter no longer than 50 characters is required as well as a roles parameter (of any length) for the launch message to be accepted. If a valid launch message is received then the DoLaunch
method will be called; this would contain application specific code to process the launch request in the knowledge that the data received can be trusted.
-
See https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP ↩︎
-
See also https://www.imsglobal.org/learning-tools-interoperability-verifying-launch-messages which also includes some sample code ↩︎