Skip to content

Dev: Configuration for LTI 1.3 Development

Lance E Sloan (lsloan) edited this page Dec 18, 2020 · 8 revisions

ℹ️ Coming Soon

This document will soon be replaced when MyLA's new feature for LTI v1.3 configuration by URL is released. To preview the new document, see: Dev: Configuration for LTI 1.3 Devemopment NEW.

Your patience while these updates are made is appreciated. Your feedback is welcome.

Configure MyLA

To complete the steps in the next section, MyLA must be configured according to the project's README.md document. It needs to be installed on a server that is accessible by Canvas. Refer to the next section for installing a simple server for development purposes.

Install Server

LTI 1.3 requires the client, an LMS like Canvas, to contact the tool server, which hosts MyLA. That means when MyLA runs on a computer in a development environment, it needs to be available online in a way that the LMS can contact it via HTTPS. There are a number of ways that can be accomplished, but the easiest is to install ngrok and run it. It will dynamically create a hostname for the application and give a URL for it that can be accessed anywhere online. Requests to that URL will be forwarded to the local computer through a network tunnel.

  • Follow the installation instructions on ngrok's download page.

    • Alternate installation methods (may not be officially supported by ngrok.com)
      • Using Homebrew (macOS or Linux)

        brew cask install ngrok
      • Using npm

        npm install ngrok
  • Add .ngrok.io to the ALLOWED_HOSTS array of MyLA's env.json configuration file. For example:

    "ALLOWED_HOSTS": [
            "127.0.0.1",
            "localhost",
            ".ngrok.io"
        ],
  • Start MyLA on the local computer using Docker:

    docker-compose up

    It runs on port 5001 by default.

  • Start a ngrok forwarding tunnel:

    ngrok http 5001

    💡 – ngrok's "http" tunnel automatically handles both HTTP and HTTPS.

    While ngrok runs, it offers some helpful troubleshooting and debugging interfaces:

    • https://dashboard.ngrok.com/status/tunnels – Shows the active forwarding tunnels for the local computer. This requires free registration with ngrok.com. Additional features become available with a paid registration.

    • http://127.0.0.1:4040 – View the incoming requests and inspect them.

    • To programmatically get the URL of the first running tunnel:

      # if jq is installed
      curl -s http://127.0.0.1:4040/api/tunnels | \
        jq -r '.tunnels[0].public_url'
      
      # without jq
      curl -s http://127.0.0.1:4040/api/tunnels | \
        sed 's/^.*"public_url":"\([^"]*\)".*$/\1/'

Once a server is hosting

Generate Keys

To enable LTI launch requests, MyLA needs a public and private RS256 key pair in PEM format. For some LTI features, the LMS need to use a JWK format of the public key to communicate with MyLA, so that may be needed as well. For the most part, the JWK public key should not be used. It's safer to give a URL to MyLA's /lti/jwks/ service instead, which generates the JWK public key from the PEM public key on demand. It is still generated here mostly for the purposes of troubleshooting the configuration.

💡 – When configuring MyLA's LTI support it is important to note that the two formats of public keys are used for different purposes.

  • The public key PEM is used for LTI launch requests.
  • The public key JWK is used by the LMS for secure communication with an already running LTI.

There are currently two ways to generate those three key files.

Django Management Tool

🚧 – NEW: This section documents a new feature that will be available soon.

MyLA includes a key generation program, createkeys, that can be run via the Django management tool, manage.py. While MyLA is running in a Docker container, enter the following command to create key files:

docker exec -it student_dashboard python manage.py createkeys

The program will create the new key files in the same directory that holds the env.json configuration file.

It will create three files with the suffixes _private.pem, _public.pem, and _public-jwk.json. By default, the three files will be named with a timestamp of the format YYYYmmddHHMMSS, followed by one of the suffixes. If you want to specify a different name for the key files, use the --basename option:

docker exec -it student_dashboard python manage.py createkeys --basename example

That will create key files named example_private.pem, example_public.pem, and example_public-jwk.json.

Standalone Program

🚧 – DEPRECATED: This section is deprecated and will be removed as soon as the feature described in the "Django Management Tool" section above becomes available.

The Python program below generates, the three key files required:

# Prerequisites:
#   pip install pycryptodome
#   pip install jwcrypto
import json

from Crypto.PublicKey import RSA
from jwcrypto.jwk import JWK

private_key_output_filename = 'example_private.pem'
public_key_output_filename = 'example_public.pem'
jwk_output_filename = 'example_public-jwk.json'

print('Generating private and public keys...')
key = RSA.generate(4096)

print('Preparing private and public key strings...')
private_key = key.exportKey()
public_key = key.publickey().exportKey()

print(f'Writing private key to file "{private_key_output_filename}"...')
with open(private_key_output_filename, 'w') as f:
    f.writelines((private_key.decode('utf-8'), '\n'))

print(f'Writing public key to file "{public_key_output_filename}"...')
with open(public_key_output_filename, 'w') as f:
    f.writelines((public_key.decode('utf-8'), '\n'))

jwk_obj = JWK.from_pem(publicKey)
public_jwk = {
    **json.loads(jwk_obj.export_public()),
    **{'alg': 'RS256', 'use': 'sig'}
}

print(f'Writing JWK to file "{jwk_output_filename}"...')
with open(jwk_output_filename, 'w') as f:
    f.writelines((json.dumps(public_jwk), '\n'))

That will create key files named example_private.pem, example_public.pem, and example_public-jwk.json.

Move the three key files to the same directory as the env.json configuration file for MyLA.

Configure security settings

  • The CSP block of the settings can be copied and should work "as-is". ngrok.io is part of those settings but would change for your production server.
  • Change "REPORT_ONLY" to false if there are no console errors in your browser when testing
  • You'll also need "CSRF_COOKIE_SECURE": true when running on HTTPS.

Configure LTI settings

⚠️ – The hostname EXAMPLE_NGROK_ID.ngrok.io appears several times in the following configuration examples. It must be replaced with the hostname of the computer on which MyLA is being tested.

  • There are a number of LTI settings including

    • "STUDENT_DASHBOARD_LTI" : true
      This setting enables LTI support.
  • Create Developer keys (Go to Canvas "Key Settings" page, following these instructions)

Use ONE of the two following sections, "Manual Entry" or "Paste JSON"

Manual Entry method

  • Redirect URIs: https://EXAMPLE_NGROK_ID.ngrok.io/lti/launch/

  • Target Link URI: https://EXAMPLE_NGROK_ID.ngrok.io/lti/launch/

  • OpenID Connect Initiation Url: https://EXAMPLE_NGROK_ID.ngrok.io/lti/login/

  • JWK Method: Public JWK URL

    • Public JWK URL: https://EXAMPLE_NGROK_ID.ngrok.io/lti/jwks/

      💡 – In the near future, the LTI specification will require JWK to be specified by URL only. To ensure compatibility, do not paste the contents of the JWK into a configuration property. Only use the public_jwk_url property to provide a URL to MyLA's JWK, like https://EXAMPLE_NGROK_ID.ngrok.io/lti/jwks/.

  • LTI Advantage Services: Enable all toggles

  • Additional Settings

    • Domain: EXAMPLE_NGROK_ID.ngrok.io

    • Custom Fields

    user_username=$User.username
    canvas_user_id=$Canvas.user.id
    canvas_course_id=$Canvas.course.id
    • Privacy Level: Public

    • Placements: Choose "Course Navigation" only, remove any others added by default

      • Course Navigation section
        • Target Link URI: https://EXAMPLE_NGROK_ID.ngrok.io/lti/launch/
    • Click the "Save" button

    • Find the new key and click the ✎ (pencil) icon to edit it.

    • Select the "Paste JSON" method if it's not selected already

    • In the "LTI 1.3 Configuration" field, find the "placements" array

    • In that array, add the following to the first object:

      "default": "disabled",

      That will prevent the tool from being installed in courses by default.

Paste JSON method

  • Copy the following JSON and paste into the "LTI 1.3 Configuration" field

    • Change the title and description attributes, if desired.

    💡 – In the near future, the LTI specification will require JWK to be specified by URL only. To ensure compatibility, do not paste the contents of the JWK into a configuration property. Only use the public_jwk_url property to provide a URL to MyLA's JWK, like https://EXAMPLE_NGROK_ID.ngrok.io/lti/jwks/.

    {
        "title": "My Learning Analytics Test",
        "scopes": [
            "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
            "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly",
            "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
            "https://purl.imsglobal.org/spec/lti-ags/scope/score",
            "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly",
            "https://canvas.instructure.com/lti/public_jwk/scope/update",
            "https://canvas.instructure.com/lti/account_lookup/scope/show",
            "https://canvas.instructure.com/lti/data_services/scope/create",
            "https://canvas.instructure.com/lti/data_services/scope/show",
            "https://canvas.instructure.com/lti/data_services/scope/update",
            "https://canvas.instructure.com/lti/data_services/scope/list",
            "https://canvas.instructure.com/lti/data_services/scope/destroy",
            "https://canvas.instructure.com/lti/data_services/scope/list_event_types",
            "https://canvas.instructure.com/lti/feature_flags/scope/show"
        ],
        "extensions": [
            {
                "domain": "EXAMPLE_NGROK_ID.ngrok.io",
                "platform": "canvas.instructure.com",
                "settings": {
                    "platform": "canvas.instructure.com",
                    "placements": [
                        {
                            "default": "disabled",
                            "placement": "course_navigation",
                            "message_type": "LtiResourceLinkRequest",
                            "target_link_uri": "https://EXAMPLE_NGROK_ID.ngrok.io/lti/launch/"
                        }
                    ]
                },
                "privacy_level": "public"
            }
        ],
        "public_jwk": {},
        "description": "This is LTI key for LTI1.3",
        "custom_fields": {
            "user_username": "$User.username",
            "canvas_user_id": "$Canvas.user.id",
            "canvas_course_id": "$Canvas.course.id"
        },
        "public_jwk_url": "https://EXAMPLE_NGROK_ID.ngrok.io/lti/jwks/",
        "target_link_uri": "https://EXAMPLE_NGROK_ID.ngrok.io/lti/launch/",
        "oidc_initiation_url": "https://EXAMPLE_NGROK_ID.ngrok.io/lti/login/"
    }

For Either Method

  • Change "State" from OFF to ON for the newly created key

  • Install the LTI APP to your course/subaccount/root account as you like, since with the default: disabled option the tool won't enabled in course navigation

  • Create new External App: "Settings -> Apps -> +App"

  • Choose "Configuration Type: by ClientID"

  • Insert "ClientID" from the created Key (value from Details column)

  • Update settings in LTI_CONFIG section of env.json

    "https://canvas.instructure.com": [
      {
        "/* requests without ID from this platform use this config by default": "*/",
      "default": true,
        
        "/* from Canvas: Developer Keys -> value from Details column": "*/",
        "client_id": "10000000000004",
        
        "/* static auth_login_url URL from this platform": "*/",
        "auth_login_url": "http://canvas.instructure.com/api/lti/authorize_redirect",
    
        "/* static auth_token_url URL from this platform": "*/",
        "auth_token_url": "http://canvas.instructure.com/login/oauth2/token",
        
        "/* static URL to get public key from this platform": "*/",
        "key_set_url": "http://canvas.instructure.com/api/lti/security/jwks",
        
        "/* this tool's private key ": "*/",
        "private_key_file": "/secrets/example_private.pem",
        
        "/* this tool's public key ": "*/",
        "public_key_file": "/secrets/example_public.pem",
        
        "/* Go to Canvas where this tool installed, click on the setting button (a gear icon, similar to '⚙️'), click 'Deployment Id', copy it, and paste here": "*/",
        "deployment_ids": [
          "6:8865aa05b4b79b64a91a86042e43af5ea8ae79eb"
        ]
      }
    ]
  • Enable the LTI in the Course navigation, depending on where you have installed the LTI tool (course, root account, or subaccount)

    • Go to the course where MyLA was installed
    • Settings -> Navigation
    • Enable the MyLA tool and save
    • Tool will appear in left navigation

After the configuration changes are complete, the MyLA Docker pods will probably need to be restarted in order for them to take effect.

Testing

After launch the tool from course navigation if the course doesn't exits in MyLA DB it will be created only by instructor and MyLA Django admin's only and UI may display message something like "Course data is not pulled in yet" Make yourself as admin locally by going to django_auth table and change the is_superuser column value to 1. Run the cron the course data will be pulled in and when the LTI launched you should get see courses view page Then if you "Act as a User" in that course in Canvas, click the link in the Navigation you should be able to get right in.

When developing locally with ngrok and webpack watch mode the initial launch takes time since the main-*.js is not compressed in watch mode. This shouldn't be an issue when running in prod mode

Enabling/Disabling the standard backend

There is a setting ENABLE_BACKEND_LOGIN which is set to False now if LTI or SAML are enabled. If you set this to True the standard internal login/logout will work. This should probably only be enabled for testing.

pylti1.3 module

MyLA uses a third party module that implements LTI 1.3 workflows and validation. See the pylti1.3 GitHub repo for additional documentation.