pylti1p3: LTI 1.3 Advantage Tool

2.0.0 · active · verified Thu Apr 16

pylti1p3 is a Python library implementing the LTI 1.3 Advantage Tool specification, enabling seamless integration with LTI 1.3 platforms. It handles OAuth 2.0, JWT validation, deep linking, and various LTI services. The current version is 2.0.0, with an active release cadence addressing new LTI features, bug fixes, and Python compatibility.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to configure the `ToolConfig` and process an incoming LTI 1.3 Message Launch. It uses mock objects for the web request to make it runnable without a full web framework. In a real application, you would integrate `ToolConfig` and `MessageLaunch` with your chosen framework (e.g., Flask, Django, FastAPI) and handle the actual HTTP request object. The `id_token` in the mock request must be a valid JWT signed by the LTI platform for successful validation.

import os
import json
from pylti1p3.tool_config import ToolConfig
from pylti1p3.message_launch import MessageLaunch

# Mock a minimal request object for demonstration
# In a real application, this would come from your web framework (e.g., Flask, Django)
class MockRequest:
    def __init__(self, method='POST', headers=None, form=None, data=None):
        self.method = method
        self.headers = headers or {}
        self.form = form or {}
        self.data = data # raw body for content_type application/json, etc.

    def get_json(self):
        return json.loads(self.data) if self.data and 'application/json' in self.headers.get('Content-Type', '') else None

    def get_param(self, key, default=None):
        return self.form.get(key, default)


# 1. Configure the LTI Tool
# These values would typically come from environment variables, database, or a configuration file
iss = os.environ.get('LTI_ISS', 'https://example.com')
client_id = os.environ.get('LTI_CLIENT_ID', 'your-client-id')
jwks_url = os.environ.get('LTI_JWKS_URL', 'https://example.com/platform/.well-known/jwks.json')
auth_login_url = os.environ.get('LTI_AUTH_LOGIN_URL', 'https://example.com/platform/login_initiations')
auth_token_url = os.environ.get('LTI_AUTH_TOKEN_URL', 'https://example.com/platform/access_token')
deployment_id = os.environ.get('LTI_DEPLOYMENT_ID', '1') # Example deployment ID

# Your tool's private key (for signing messages sent *to* the platform)
# In a real app, this would be loaded from a file or secure store
private_key = os.environ.get('LTI_TOOL_PRIVATE_KEY', '-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----')

# Your tool's public key (to be registered with the platform)
# In a real app, this would be loaded from a file or secure store
public_key = os.environ.get('LTI_TOOL_PUBLIC_KEY', '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----')


tool_config = ToolConfig({
    'key_set_url': jwks_url,
    'iss': iss,
    'client_id': client_id,
    'deployment_ids': [deployment_id],
    'auth_login_url': auth_login_url,
    'auth_token_url': auth_token_url,
    'private_key': private_key,
    'public_key': public_key
})


# 2. Simulate an LTI 1.3 Message Launch request
# This is a highly simplified mock. A real LTI launch involves a POST request
# with a 'id_token' parameter (a signed JWT) and possibly 'state' for CSRF protection.
# For a basic example, we'll just demonstrate setting up the MessageLaunch object.

# In a real scenario, the 'id_token' would be extracted from the incoming request's form data.
# Here, we'll use a placeholder JWT string that would normally be generated by the Platform.
# Note: This placeholder JWT will NOT be valid for actual verification.
# A valid JWT needs to be signed by the platform's private key and match the JWKS.
example_id_token = os.environ.get('LTI_EXAMPLE_ID_TOKEN', 'eyJhbGciOiJSUzI1NiIsImtpZCI6IkExMjMifQ.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDUifQ.S0meS1gnedT0ken')

mock_form_data = {
    'id_token': example_id_token,
    'state': 'a_random_state_string'
}

mock_request = MockRequest(method='POST', form=mock_form_data)


# 3. Process the LTI Message Launch
# The MessageLaunch object requires a 'request' (your web framework's request object)
# and the 'ToolConfig' you just created.

try:
    message_launch = MessageLaunch(mock_request, tool_config)
    is_valid_launch = message_launch.validate()

    if is_valid_launch:
        print("LTI 1.3 Message Launch validated successfully!")
        launch_data = message_launch.get_launch_data()
        print("Launch Data (Payload):\n", json.dumps(launch_data, indent=2))

        # Example: Accessing specific claims
        print("User ID:", message_launch.get_sub()) # 'sub' is the user ID
        print("Context Title:", message_launch.get_context_title()) # Course title

        # Access LTI 1.3 services (e.g., Assignment and Grades Service)
        # This requires the launch to contain the appropriate service context and claims
        # if message_launch.has_ags():
        #     print("Assignments and Grades Service available!")
        #     ags = message_launch.get_ags()
        #     # Example: Get line items
        #     # line_items = ags.get_lineitems()
        #     # print("Line items:", line_items)

    else:
        print("LTI 1.3 Message Launch validation failed.")

except Exception as e:
    print(f"An error occurred during LTI launch processing: {e}")
    # In a real app, handle authentication/authorization errors gracefully

view raw JSON →