Getting Started#

LTI 1.3 is an open standard. Many Learning Management System (LMS) vendors support the LTI 1.3 standard and as such vendors are able to integrate with various LMS’s as External Tools.

Start by navigating to your LMS vendor’s integration section to register the installation and configuration steps. During the tool registration process, you should obtain the following information which is necessary to complete the setup of the LTI Authenticator plugin:

  • issuer: URL of the LMS platform used for identification

  • client_id: opaque ID of the tool registration at the platform. You may obtain multiple client IDs, e.g. if you do multiple single-tenant registrations within your LMS with the same JupyterHub instance.

Note

If your LMS is not listed feel free to send us a PR with instructions for this new LMS.

Then, follow the steps below to configure your JupyterHub with the basic settings. See the configuration reference for a complete list of available configuration options.

Basic Settings#

The required settings to get authentication via LTI 1.3 to work are:

  • issuer: the URL of your LMS platform. If your LMS is served from https://canvas.instructure.com, the issuer is https://canvas.instructure.com.

  • client_id: set or list of opaque IDs, typically generated by the LMS when a tool is registered there.

  • authorize_url: Authorization endpoint of the LMS platform. The URL to which authorization requests are sent by the authenticator as part of the OIDC implicit flow. E.g. https://canvas.instructure.com/api/lti/authorize_redirect.

  • jwks_endpoint: An endpoint of the LMS from which JupyterHub can obtain the JWKS to verify and decode any received JWT. E.g. https://canvas.instructure.com/api/lti/security/jwks.

A valid minimal configuration in the jupyterhub_config.py may look like this

c.JupyterHub.authenticator_class = "ltiauthenticator.lti13.auth.LTI13Authenticator"

# Define issuer identifier of the LMS platform
c.LTI13Authenticator.issuer = "https://canvas.instructure.com"

# Add the LTI 1.3 configuration options
c.LTI13Authenticator.authorize_url = "https://canvas.instructure.com/api/lti/authorize_redirect"

# The platform's JWKS endpoint url providing public key sets used to verify the ID token
c.LTI13Authenticator.jwks_endpoint = "https://canvas.instructure.com/api/lti/security/jwks"

# The external tool's client id as represented within the platform (LMS)
c.LTI13Authenticator.client_id = ["125900000000000329"]

Username Key Setting#

The username is inferred from the ID token sent by the platform (LMS) during the login flow based on the setting of the username_key traitlet. The default is email but all other top-level claims of the ID token may be chosen.

Danger

Make sure to use a claim that has a unique value for each user. If that is not the case, two distinct platform user may share the same user on Jupyterhub.

The list of available values depends on the LMS vendor you are using and how your LMS is configured, but you may take a look at this example to get an idea of the available values. If you are in doubt about the content of the ID token sent by your LMS, you may use an external test tool with your LMS to capture the ID token.

Your LMS may provide additional keys in the LTI 1.3 login initiation flow that you can use to set the username. In most cases these are located in the https://purl.imsglobal.org/spec/lti/claim/custom claim. In this case, username_key must be prefixed with “custom_”. For example, username_key="custom_uname" will set the username to the value of the parameter uname within the https://purl.imsglobal.org/spec/lti/claim/custom claim.

You may also have the option of using variable substitutions to fetch values that aren’t provided with your vendor’s standard LTI 1.3 login initiation flow request.

If your platform’s LTI 1.3 settings are defined with privacy enabled or if the given username_key is not found within the ID token, then by default the sub claim is used to set the username. This claim contains a unique and opaque user ID generated by the platform.

The example below illustrates how to fetch the user’s given name to set the JupyterHub username:

# Set the user's email as their user id
c.LTI13Authenticator.username_key = "email"

Configuration JSON Settings#

The LTI 1.3 configuration JSON settings are available at /lti13/config endpoint. Some LMS vendors accept URLs that render the configuration JSON to simplify the LTI 1.3 tool installation process. You may customize these settings with the tool_name and tool_description configuration options.

Custom Configuration with JupyterHub’s Helm Chart#

If you are running JupyterHub within a Kubernetes Cluster, deployed using helm, you need to supply the LTI 1.3 (OIDC/OAuth2) endpoints and required out-of-band registration settings. The example below also demonstrates how customize the username_key to set the user’s give name:

# Custom config for JupyterHub's helm chart
hub:
  config:
    # Additional documentation related to authentication and authorization available at
    # https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/authentication.html
    JupyterHub:
      authenticator_class: ltiauthenticator.lti13.auth.LTI13Authenticator
    LTI13Authenticator:
      # Use an LTI 1.3 claim to set the username. You can use and LTI 1.3 claim that
      # identifies the user, such as email, last_name, etc.
      username_key: "given_name"
      # The issuer identifyer of the platform
      issuer: "https://canvas.instructure.com"
      # The LTI 1.3 authorization url
      authorize_url: "https://canvas.instructure.com/api/lti/authorize_redirect"
      # The external tool's client id as represented within the platform (LMS)
      # Typically created by the platform when registering the tool.
      client_id:
        - "125900000000000329"
      # The platform's JWKS endpoint url providing public key sets used to verify the ID token
      jwks_endpoint: "https://canvas.instructure.com/api/lti/security/jwks"

Deal with Synchronization Issues (iat, nbf, exp)#

The underlying OIDC Implicit flow protocol requires some checks involving token issuing time. In particular, a token must not be issued in the future. Since the LMS and JupyterHub are likely not running on the same machine, it is possible that those two machines may have unsynchronized clocks. This may lead to false rejection of a valid token with an HTTP Code 400: "The token is not yet valid (...)". To deal with this situation, it is possible to configure a leeway time in seconds to relax the strictness of the time-based checks. The following configuration will allow for a discrepancy of 10 seconds between the clocks of both servers

c.LTI13LaunchValidator.time_leeway = 10

Note

The class being configured is LTI13LaunchValidator. For a complete list of its configuration options, see the configuration reference.

Deal with Incorrect URI Scheme detection when running behind a reverse proxy#

By default, the scheme (“https” or “http”) used for creating the URLs of the authenticators endpoints via string interpolation is inferred from the incoming request’s header. However, there is no universal standard if and how proxies add or append such information to the header of proxied requests. There are the Forwarded, X-Scheme and X-Forwarded-* header, to name the most commonly used. When your JupyterHub runs behind multiple reverse proxies where somewhere TLS termination is happening, the inferred scheme might be incorrectly detected if those proxies use a mixture of the above-mentioned header or a custom one. In such situations, it is possible to manually specify the scheme to either "https" or "http", e.g.

c.LTI13Authenticator.uri_scheme = "https"