Skip to content

LTI 1.3 & SCORM Integration β€” Testing Guide

Prerequisites

  • Access to a Canvas, Moodle, or Blackboard sandbox/dev instance
  • Admin access to an Accessible PDF Converter tenant
  • The migration has been applied: supabase migration up or deployed via CI

1. Generate Tool Keys

Before any LTI launch can work, the tool needs at least one RSA key pair in the lti_keys table. Run this once against your Supabase instance:

-- Run in Supabase SQL Editor or via psql
-- This inserts a placeholder row. The actual key generation happens at runtime
-- via the /api/lti/jwks endpoint when keys are present.

Or use the API to generate keys programmatically. You can call generateRsaKeyPair() from a one-off script:

import { generateRsaKeyPair } from './workers/api/src/utils/lti-jwt';
const keys = await generateRsaKeyPair();
console.log(JSON.stringify(keys, null, 2));
// Insert the output into the lti_keys table:
// INSERT INTO lti_keys (kid, public_key_jwk, private_key_jwk, active)
// VALUES ('<kid>', '<public_key_jwk>', '<private_key_jwk>', true);

Verify the JWKS endpoint returns your key:

Terminal window
curl https://api-pdf.theaccessible.org/api/lti/jwks
# Should return: {"keys": [{"kty":"RSA","kid":"...","alg":"RS256","use":"sig","n":"...","e":"AQAB"}]}

2. Register a Platform (Canvas Example)

A. In Canvas

  1. Go to Admin > Developer Keys > + Developer Key > LTI Key
  2. Configure:
    • Key Name: Accessible PDF Converter
    • Redirect URIs: https://api-pdf.theaccessible.org/api/lti/callback
    • Target Link URI: https://api-pdf.theaccessible.org/api/lti/callback
    • OpenID Connect Initiation URL: https://api-pdf.theaccessible.org/api/lti/login
    • JWK Method: Public JWK URL
    • Public JWK URL: https://api-pdf.theaccessible.org/api/lti/jwks
  3. Save β€” note the Client ID (a long number like 10000000000042)
  4. Set the key state to ON
  5. Go to Settings > Apps > + App > By Client ID and paste the Client ID

B. In the Accessible PDF Converter

  1. Log in as a tenant admin
  2. Go to Tenant Admin > LTI Platform Management (/tenant-admin/lti)
  3. Click Register Platform and fill in:
    • Platform Name: Canvas Sandbox
    • Issuer URL: https://canvas.instructure.com (or your Canvas domain)
    • Client ID: the Client ID from step A.3
    • Authorization Endpoint: https://<your-canvas>/api/lti/authorize_redirect
    • Token Endpoint: https://<your-canvas>/login/oauth2/token
    • JWKS Endpoint: https://<your-canvas>/api/lti/security/jwks
    • Deployment ID: (leave blank for Canvas, or use the deployment ID from the External Tool settings)
  4. Click Register β€” the tool configuration values will be displayed

Canvas Endpoint Cheat Sheet

FieldCanvas URL
Issuerhttps://canvas.instructure.com
Auth Endpointhttps://<domain>/api/lti/authorize_redirect
Token Endpointhttps://<domain>/login/oauth2/token
JWKS Endpointhttps://<domain>/api/lti/security/jwks

Moodle Endpoint Cheat Sheet

FieldMoodle URL
Issuerhttps://<domain>
Auth Endpointhttps://<domain>/mod/lti/auth.php
Token Endpointhttps://<domain>/mod/lti/token.php
JWKS Endpointhttps://<domain>/mod/lti/certs.php

Blackboard Endpoint Cheat Sheet

FieldBlackboard URL
Issuerhttps://blackboard.com
Auth Endpointhttps://developer.blackboard.com/api/v1/gateway/oidcauth
Token Endpointhttps://developer.blackboard.com/api/v1/gateway/oauth2/jwttoken
JWKS Endpointhttps://developer.blackboard.com/api/v1/management/applications/<app-id>/jwks.json

3. Test LTI Launch

  1. In Canvas, create a course and add the tool as an External Tool assignment or module item
  2. Click the tool link β€” you should see:
    • A brief redirect through Canvas OAuth
    • The converter loads in an iframe (no header/footer)
    • You are automatically signed in
  3. Verify in the database:
    • lti_user_mappings has a new row mapping your Canvas user to a Supabase user
    • profiles table shows the user with the correct tenant_id

Troubleshooting Launch Failures

SymptomLikely Cause
”Unregistered LTI platform”Issuer URL or Client ID doesn’t match registration
”JWT verification failed”JWKS endpoint unreachable, or key rotation happened (clear KV cache: delete lti-jwks:* keys)
β€œInvalid or expired state”KV state expired (5 min TTL) β€” retry the launch
Blank iframe / X-Frame-Options error_headers file not deployed β€” check Cloudflare Pages build
”Failed to create session”Supabase service role key issue β€” check SUPABASE_SERVICE_ROLE_KEY secret

4. Test SCORM Export

  1. Upload and convert a PDF through the normal UI
  2. Once conversion is complete, call the SCORM export endpoint:
Terminal window
curl -H "Authorization: Bearer <your-token>" \
https://api-pdf.theaccessible.org/api/export/<fileId>/scorm \
-o test-scorm.zip
  1. Verify the ZIP contents:
Terminal window
unzip -l test-scorm.zip
# Should contain:
# imsmanifest.xml
# scorm-api.js
# index.html
  1. Validate the manifest:

    • Open imsmanifest.xml β€” should have valid XML with <schema>ADL SCORM</schema> and <schemaversion>1.2</schemaversion>
    • The <resource> element should reference index.html with adlcp:scormtype="sco"
  2. Import into an LMS:

    • Canvas: Course > Modules > + > External Tool or import as SCORM package via course import
    • Moodle: Add Activity > SCORM package > Upload the ZIP
    • SCORM Cloud (https://cloud.scorm.com): Free testing tool β€” upload the ZIP and launch
  3. Verify:

    • The content displays correctly (styles preserved)
    • The SCORM API initializes (LMSInitialize called)
    • On page load, lesson_status is set to completed
    • On close, LMSFinish is called
    • In the LMS gradebook, the item shows as completed

5. Test Nonce Replay Protection

  1. Capture a valid LTI launch id_token (e.g., from browser dev tools Network tab during a launch)
  2. Replay the same token with the same nonce β€” should get LTI_NONCE_REPLAY error
  3. Verify old nonces are cleaned up: run SELECT cleanup_lti_nonces(); in SQL Editor, then check lti_nonces table

6. Test Tenant Admin CRUD

Terminal window
# List platforms
curl -H "Authorization: Bearer <token>" \
https://api-pdf.theaccessible.org/api/tenant-admin/lti-platforms
# Create
curl -X POST -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"Test LMS","issuer":"https://test.example.com","clientId":"123","authEndpoint":"https://test.example.com/auth","tokenEndpoint":"https://test.example.com/token","jwksEndpoint":"https://test.example.com/jwks"}' \
https://api-pdf.theaccessible.org/api/tenant-admin/lti-platforms
# Update
curl -X PUT -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"Updated Name","status":"inactive"}' \
https://api-pdf.theaccessible.org/api/tenant-admin/lti-platforms/<platformId>
# Delete
curl -X DELETE -H "Authorization: Bearer <token>" \
https://api-pdf.theaccessible.org/api/tenant-admin/lti-platforms/<platformId>

7. Test Embedded UI

  1. Navigate directly to /lti/launch?platformId=<id> without LTI tokens β€” should show β€œNo LTI session found” error
  2. After a successful LTI launch, verify:
    • No header/footer/navigation visible
    • Upload and conversion work normally
    • The page works inside an iframe (test with a simple HTML page that embeds it)
<!-- test-iframe.html β€” open in browser to test embedding -->
<iframe src="https://pdf.theaccessible.org/lti/launch?platformId=test"
width="100%" height="800" style="border:1px solid #ccc;"></iframe>