Sharebar?

Step 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 will insert the following parameters into the data being passed in the message:

  • oauth_callback - not used by LTI so 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 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 secret associated with the consumer key for the message, and a method to confirm that the nonce value is not a repeat.  The tasks it carries out are:

  1. check that the timestamp is within a specified interval either side of the current server time (typically ±5 minutes);
  2. 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);
  3. generate the signature for the message using the secret for the tool consumer and compare the value with the one received.

If a 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.

Sample PHP Code

The following code assumes that an associative array named $tool_consumer_secrets has already been initialized with the keys and secrets of each tool consumer registered by the tool provider.  For example, a tool consumer with a key of imsglobal.org and a secret of ThisIsABigSecret! would have an entry in the array equivalent to:

  $tool_consumer_secrets['imsglobal.org'] = 'ThisIsABigSecret!';

This array would typcially be generated as a result of a database query.

The $ok variable is carried over from Step 1 and should have a value of true at the start of this code segment.  After the code has been executed the variable will have been set to false if the message has not been properly signed (or is an out-of-date or duplicate request).


  // Check the consumer key is recognised
  $ok = $ok && array_key_exists($_POST['oauth_consumer_key'], $tool_consumer_secrets);

  // Check the OAuth credentials (nonce, timestamp and signature)
  if ($ok) {
    require_once('lib.php');  // load class to act as an OAuth data store
    try {
      $store = new ImsOAuthDataStore($_POST['oauth_consumer_key'], $CONSUMERS_TABLE[$_POST['oauth_consumer_key']]);
      $server = new OAuthServer($store);
      $method = new OAuthSignatureMethod_HMAC_SHA1();
      $server->add_signature_method($method);
      $request = OAuthRequest::from_request();
      $server->verify_request($request);
    } catch (Exception $e) {
      $ok = FALSE;
    }
  }

ImsOAuthDataStore Class

This is a simple class to provide OAuth with access to the consumer key and secret during its signature verification.  It can be enhanced to include the checking of nonce values as well.

  class ImsOAuthDataStore extends OAuthDataStore {

    private $consumer_key = NULL;
    private $consumer_secret = NULL;

    public function __construct($consumer_key, $consumer_secret) {

      $this->consumer_key = $consumer_key;
      $this->consumer_secret = $consumer_secret;

    }

    function lookup_consumer($consumer_key) {

      return new OAuthConsumer($this->consumer_key, $this->consumer_secret);

    }

    function lookup_token($consumer, $token_type, $token) {

      return new OAuthToken($consumer, '');

    }

    function lookup_nonce($consumer, $token, $nonce, $timestamp) {

      return FALSE;  // If a persistent store is available nonce values should be retained for a period and checked here

    }

    function new_request_token($consumer, $callback = null) {

      return NULL;

    }

    function new_access_token($token, $consumer, $verifier = null) {

      return NULL;

    }

  }

See Also