This code consists of two parts: A library for creating LTI tool providers in PHP. An collection of example tools that utilize the library.
The example is all written in PHP, and it also contains a docker compose file for easy setup if you have docker installed.
First thing you will need is to configure your registration and deployment in the example code's fake registrations database.
This can be found in each example tool's code at db/configs/local.json
.
To configure your registration add a JSON object into the local.json
file in the following format.
{
"<issuer>" : { // This will usually look something like 'http://example.com'
"client_id" : "<client_id>", // This is the id received in the 'aud' during a launch
"auth_login_url" : "<auth_login_url>", // The platform's OIDC login endpoint
"auth_token_url" : "<auth_token_url>", // The platform's service authorization endpoint
"key_set_url" : "<key_set_url>", // The platform's JWKS endpoint
"private_key_file" : "<path_to_private_key>", // Relative path to the tool's private key
"deployment" : {
"<deployment_id>" : "<deployment_id>" // The deployment_id passed by the platform during launch
}
}
}
To run in docker you will need both docker
and docker-compose
To get the examples up and running in docker simply run:
docker-compose up --build
There are now two example tools you can launch into on the port 9001:
Simple Example:
OIDC Login URL http://localhost:9001/example/login.php
LTI Launch URL http://localhost:9001/example/launch.php
Game Example:
OIDC Login URL http://localhost:9001/game_example/login.php
LTI Launch URL http://localhost:9001/game_example/game.php
You're now free to launch in and use the tool.
To import the library, copy the lti
folder inside src
into your project and use the following code at the beginning of execution:
require_once('lti/lti.php');
use \IMSGlobal\LTI;
Coming soon...
To allow for launches to be validated and to allow the tool to know where it has to make calls to, registration data must be stored.
Rather than dictating how this is store, the library instead provides an interface that must be implemented to allow it to access registration data.
The LTI\Database
interface must be fully implemented for this to work.
class Example_Database implements LTI\Database {
public function find_registration_by_issuer($iss) {
...
}
public function find_deployment($iss, $deployment_id) {
...
}
}
The find_registration_by_issuer
method must return an LTI\LtiRegistration
.
return LTI\LtiRegistration::new()
->set_auth_login_url($auth_login_url)
->set_auth_token_url($auth_token_url)
->set_client_id($client_id)
->set_key_set_url($key_set_url)
->set_issuer($issuer)
->set_tool_private_key($private_key);
The find_deployment
method must return an LTI\LtiDeployment
if it exists within the database.
return LTI\LtiDeployment::new()
->set_deployment_id($deployment_id);
Calls into the Library will require an instance of LTI\Database
to be passed into them.
LTI 1.3 uses a modified version of the OpenId Connect third party initiate login flow. This means that to do an LTI 1.3 launch, you must first receive a login initialization request and return to the platform.
To handle this request, you must first create a new LTI\LtiOidcLogin
object.
$login = LtiOidcLogin::new(new Example_Database());
Now you must configure your login request with a return url (this must be preconfigured and white-listed on the tool).
If a redirect url is not given or the registration does not exist an LTI\OidcException
will be thrown.
try {
$redirect = $login->do_oidc_login_redirect("https://my.tool/launch");
} catch (LTI\OidcException $e) {
echo 'Error doing OIDC login';
}
With the redirect, we can now redirect the user back to the tool. There are three ways to do this:
This will add a 302 location header and then exit.
$redirect->do_redirect();
This will echo out some javascript to do the redirect instead of using a 302.
$redirect->do_js_redirect();
You can also get the url you need to redirect to, with all the necessary query parameters, if you would prefer to redirect in a custom way.
$redirect_url = $redirect->get_redirect_url();
Redirect is now done, we can move onto the launch.
Now that we have done the OIDC log the platform will launch back to the tool. To handle this request, first we need to create a new LTI\LtiMessageLaunch
object.
$launch = LTI\LtiMessageLaunch::new(new Example_Database());
Once we have the message launch, we can validate it. This will check signatures and the presence of a deployment and any required parameters. If the validation fails an exception will be thrown.
try {
$launch->validate();
} catch (Exception $e) {
echo 'Launch validation failed';
}
Now we know the launch is valid we can find out more information about the launch.
Check if we have a resource launch or a deep linking launch.
if ($launch->is_resource_launch()) {
echo 'Resource Launch!';
} else if ($launch->is_deep_link_launch()) {
echo 'Deep Linking Launch!';
} else {
echo 'Unknown launch type';
}
Check which services we have access to.
if ($launch->has_ags()) {
echo 'Has Assignments and Grades Service';
}
if ($launch->has_nrps()) {
echo 'Has Names and Roles Service';
}
It is likely that you will want to refer back to a launch later during subsequent requests. This is done using the launch id to identify a cached request. The launch id can be found using:
$launch_id = $launch->get_launch_id().
Once you have the launch id, you can link it to your session and pass it along as a query parameter.
Make sure you check the launch id against the user session to prevent someone from making actions on another person's launch.
Retrieving a launch using the launch id can be done using:
$launch = LTIMessageLaunch::from_cache($launch_id, new Example_Database());
Once retrieved, you can call any of the methods on the launch object as normal, e.g.
if ($launch->has_ags()) {
echo 'Has Assignments and Grades Service';
}
If you receive a deep linking launch, it is very likely that you are going to want to respond to the deep linking request with resources for the platform.
To create a deep link response you will need to get the deep link for the current launch.
$dl = $launch->get_deep_link();
Now we are going to need to create LTI\LtiDeepLinkResource
to return.
$resource = LTI\LtiDeepLinkResource::new()
->set_url("https://my.tool/launch")
->set_custom_params(['my_param' => $my_param])
->set_title('My Resource');
Everything is set to return the resource to the platform. There are two methods of doing this.
The following method will output the html for an aut-posting form for you.
$dl->output_response_form([$resource]);
Alternatively you can just request the signed JWT that will need posting back to the platform by calling.
$dl->get_response_jwt([$resource]);
Before using names and roles you should check that you have access to it.
if (!$launch->has_nrps()) {
throw new Exception("Don't have names and roles!");
}
Once we know we can access it, we can get an instance of the service from the launch.
$nrps = $launch->get_nrps();
From the service we can get an array of all the members by calling:
$members = $nrps->get_members();
Before using assignments and grades you should check that you have access to it.
if (!$launch->has_ags()) {
throw new Exception("Don't have assignments and grades!");
}
Once we know we can access it, we can get an instance of the service from the launch.
$ags = $launch->get_ags();
To pass a grade back to the platform, you will need to create an LTI\LtiGrade
object and populate it with the necessary information.
$grade = LTI\LtiGrade::new()
->set_score_given($grade)
->set_score_maximum(100)
->set_timestamp(date(DateTime::ISO8601))
->set_activity_progress('Completed')
->set_grading_progress('FullyGraded')
->set_user_id($external_user_id);
To send the grade to the platform we can call:
$ags->put_grade($grade);
This will put the grade into the default provided lineitem. If no default lineitem exists it will create one.
If you want to send multiple types of grade back, that can be done by specifying an LTI\LtiLineItem
.
$lineitem = LTI\LtiLineItem::new()
->set_tag('grade')
->set_score_maximum(100)
->set_label('Grade');
$ags->put_grade($grade, $lineitem);
If a lineitem with the same tag
exists, that lineitem will be used, otherwise a new lineitem will be created.
If you have improvements, suggestions or bug fixes, feel free to make a pull request or issue and someone will take a look at it.
You do not need to be an IMS Member to use or contribute to this library, however it is recommended for better access to support resources and certification.
This library was initially created by @MartinLenord from Turnitin to help prove out the LTI 1.3 specification and accelerate tool development.
Note: This library is for IMS LTI 1.3 based specifications only. Requests to include custom, off-spec or vendor-specific changes will be declined.
If you don't like PHP and have a favorite language that you would like to make a library for, we'd love to hear about it!