An LTI 1.3 Tool Provider that allows external LMS platforms (Moodle, Canvas, Blackboard, etc.) to launch H5P content and receive grades via the Assignment and Grade Services (AGS).
Learning Tools Interoperability (LTI) is a standard for integrating external tools with Learning Management Systems. LTI 1.3 uses modern OAuth 2.0 / OpenID Connect authentication.
This tool provider allows any LTI 1.3 compliant LMS to:
- Launch H5P content in an iframe
- Receive completion scores back to the gradebook
- LTI 1.3 Compliant: Works with Moodle, Canvas, Blackboard, Brightspace, etc.
- OIDC Authentication: Secure OAuth 2.0 / OpenID Connect flow
- Grade Passback: Sends scores to LMS via Assignment and Grade Services
- Content Picker: Select H5P content if not specified in launch
- Python 3.8+
- H5P server running at
http://localhost:3000 - An LTI 1.3 capable LMS (Moodle, Canvas, etc.)
# From this directory
pip install -r requirements.txt
# Generate RSA keys for LTI signing
openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout -out public.key
# Configure your LMS details (see Configuration section)
# Edit tool_config.json
# Run the tool provider
python app.py| Endpoint | Purpose |
|---|---|
/lti/login |
OIDC Login Initiation URL |
/lti/launch |
Target Link URI (Launch URL) |
/.well-known/jwks.json |
JSON Web Key Set |
/lti/config |
Tool configuration (JSON) |
/lti/webhook |
Receives xAPI scores from H5P |
Saltire is a free LTI testing platform - no signup required. It's the fastest way to test your LTI tool provider.
# Terminal 1: H5P Server
cd h5p-server && npm start
# Terminal 2: LTI Provider
cd examples/lti-provider && python app.pySaltire needs to reach your local servers. You need two tunnels - one for the LTI provider and one for the H5P server:
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
# Terminal 3: Tunnel for LTI Provider
cloudflared tunnel --url http://localhost:5001
# Copy this URL for Saltire config (e.g., https://abc-random.trycloudflare.com)
# Terminal 4: Tunnel for H5P Server
cloudflared tunnel --url http://localhost:3000
# Copy this URL for H5P_BASE_URL (e.g., https://xyz-random.trycloudflare.com)Then restart the servers with the tunnel URLs:
# H5P Server (use Docker or native)
H5P_BASE_URL=https://xyz-random.trycloudflare.com docker compose up -d h5p-server
# LTI Provider
H5P_SERVER=https://xyz-random.trycloudflare.com APP_URL=https://abc-random.trycloudflare.com python app.pyNote: Avoid ngrok free tier - its interstitial warning page breaks iframes and LTI launches.
- Go to https://saltire.lti.app/platform
- Click "Security Model" in the left sidebar
- In the Security Model section at the top, set:
| Saltire Field | Your Tool Value |
|---|---|
| Message URL | https://YOUR-TUNNEL-URL/lti/launch |
- Expand "Tool Details" section below and enter:
| Saltire Field | Your Tool Value |
|---|---|
| Initiate login URL | https://YOUR-TUNNEL-URL/lti/login |
| Redirection URI(s) | https://YOUR-TUNNEL-URL/lti/launch |
| Public keyset URL | https://YOUR-TUNNEL-URL/.well-known/jwks.json |
- Click "Fetch" next to Public keyset URL to load your tool's public key
- Click "Save"
Copy values from Saltire's "Platform Details" section into tool_config.json:
| Saltire Field | tool_config.json Key |
|---|---|
| Platform/Issuer ID | (use as the top-level key) |
| Client ID | client_id |
| Deployment ID | deployment_ids (as array) |
| Authentication request URL | auth_login_url |
| Access Token service URL | auth_token_url |
| Public keyset URL | key_set_url |
Example tool_config.json:
{
"https://saltire.lti.app/platform": [{
"default": true,
"client_id": "saltire.lti.app",
"deployment_ids": ["YOUR_DEPLOYMENT_ID"],
"auth_login_url": "https://saltire.lti.app/platform/auth",
"auth_token_url": "https://saltire.lti.app/platform/token/YOUR_TOKEN_ID",
"key_set_url": "https://saltire.lti.app/platform/jwks/YOUR_JWKS_ID",
"private_key_file": "private.key",
"public_key_file": "public.key"
}]
}Important: Restart the tool after changing config: python app.py
- In Saltire, click "Connect" button (top right)
- Select "Perform launch"
- You should see the H5P content picker
- Select content and interact with it
- Grades should pass back to Saltire after completing H5P activities
- "Invalid redirect": Make sure tunnel URL matches exactly in both configs
- "JWT validation failed": Restart app.py after changing tool_config.json
- Tunnel URL changed: Cloudflared URLs change on restart - update both configs
# Generate private key
openssl genrsa -out private.key 2048
# Extract public key
openssl rsa -in private.key -pubout -out public.keyCreate tool_config.json with your LMS platform details:
{
"https://your-lms.edu": [{
"default": true,
"client_id": "client-id-from-lms",
"deployment_ids": ["deployment-id-from-lms"],
"auth_login_url": "https://your-lms.edu/mod/lti/auth.php",
"auth_token_url": "https://your-lms.edu/mod/lti/token.php",
"key_set_url": "https://your-lms.edu/mod/lti/certs.php",
"private_key_file": "private.key",
"public_key_file": "public.key"
}]
}- Go to Site Administration > Plugins > External tool > Manage tools
- Click "configure a tool manually"
- Enter:
- Tool URL:
http://your-domain:5001/lti/launch - LTI version: LTI 1.3
- Public key type: Keyset URL
- Public keyset:
http://your-domain:5001/.well-known/jwks.json - Initiate login URL:
http://your-domain:5001/lti/login - Redirection URI(s):
http://your-domain:5001/lti/launch
- Tool URL:
- Go to Admin > Developer Keys > + Developer Key > + LTI Key
- Configure with the same endpoints
1. User clicks H5P activity in LMS
2. LMS redirects to /lti/login (OIDC initiation)
3. Tool provider redirects to LMS auth endpoint
4. User authenticates with LMS
5. LMS POST to /lti/launch with JWT
6. Tool validates JWT and extracts user/content info
7. H5P player shown in iframe
8. User completes activity
9. H5P sends score to /lti/webhook
10. Tool sends grade to LMS via AGS
Pass the H5P content ID in the LTI launch custom parameters:
h5p_content_id=your-content-id
If not provided, users will see a content picker to choose content.
Grades are sent to the LMS using LTI Assignment and Grade Services (AGS):
- H5P player sends xAPI completion event
- Tool receives via
/lti/webhook - Tool stores grade and sends to LMS AGS endpoint
- Grade appears in LMS gradebook
examples/lti-provider/
├── app.py # Flask LTI tool provider
├── requirements.txt # Python dependencies
├── tool_config.json # LMS configuration (create this)
├── private.key # RSA private key (generate this)
├── public.key # RSA public key (generate this)
├── lti_data.db # SQLite database (auto-created)
└── README.md # This file
SECRET_KEY=your-secret-key # Flask session secret
H5P_SERVER=http://localhost:3000 # H5P server URL
APP_URL=http://localhost:5001 # This tool's URL- Production: Use HTTPS for all endpoints
- Keys: Keep
private.keysecret, never commit to git - Sessions: Use Redis or database-backed sessions in production
- Validation: The tool validates all LTI messages using JWT signatures
- Check that
tool_config.jsonhas correct LMS details - Verify client_id and deployment_ids match LMS configuration
- Regenerate RSA keys
- Ensure public key is correctly registered in LMS
- Verify LMS has AGS enabled for the tool
- Check that tool has score scope in LMS configuration
- Requires Moodle 3.7+ for LTI 1.3
- Enable "Accept grades from the tool" in tool settings
- Enable "Privacy: Send User Data" if needed
- Configure scopes in Developer Key settings
- Use Blackboard Learn REST API for additional features
- Configure placement to enable deep linking