Skip to content

Package: lti

Generated from api/packages/lti

Source: api/packages/lti. The sections after the README are extracted from the code on every docs build.

Both directions of LTI 1.3 for every tenant (ADR 0012):

  • Platform: ulams launches external tools inside lessons (topic type LtiLink), receives grades through AGS 2.0, shares the course member list through NRPS 2.0 (per tool, off by default) and lets authors pick content in the tool (Deep Linking 2.0).
  • Tool: Moodle, Canvas and other LMSs launch ulams courses, with grade passback and a course picker for deep linking. Launch validation by packbackbooks/lti-1p3-tool (Apache-2.0).
Who calls Endpoint Purpose
anyone GET /api/lti/jwks, GET /.well-known/jwks.json Our public keys (next, active, recently retired)
admin (lti_manage) GET /api/admin/lti/endpoints Issuer and URLs to register us elsewhere
admin (lti_manage) /api/admin/lti/tools (CRUD), POST /api/admin/lti/tools/{tool}/deep-link Tools we launch; start deep linking into a lesson
admin (lti_manage) /api/admin/lti/platforms (CRUD) Platforms that may launch us
learner POST /api/lti/launches/{topic} OIDC login URL of the topic’s tool (2-minute, single-use hint)
tool GET/POST /api/lti/platform/authorize OIDC auth: posts the signed id_token to the tool
tool POST /api/lti/platform/token AGS access token (client credentials, JWT assertion)
tool /api/lti/platform/ags/{course}/lineitems[/{id}[/scores|/results]] AGS line items, scores, results
tool GET /api/lti/platform/nrps/{course} NRPS 2.0 membership container (scope contextmembership.readonly, nrps_enabled on the tool; limit, page, role; Link rel=next)
tool POST /api/lti/platform/deep-links Deep-linking response: creates LtiLink topics
platform GET/POST /api/lti/tool/login, POST /api/lti/tool/launch OIDC login initiation and launch
learner’s browser POST /api/lti/tool/launch/verify Second step when the login used the platform’s storage (lti_storage_target, Client Side postMessage Storage): checks the value read from the platform against the login’s nonce
learner’s browser POST /api/lti/tool/deep-link Returns the picked courses to the platform
front POST /api/lti/tool/exchange One-time launch code to a Passport token

A tool in ulams (platform side): POST /api/admin/lti/tools with the tool’s OIDC login URL, launch URL, optional deep-linking URL, and its JWKS URL or PEM public key. ulams issues client_id and deployment_id; give the tool those plus the issuer, oidc_auth_url, token_url and jwks_url from GET /api/admin/lti/endpoints.

ulams in Moodle (tool side): in Moodle add an external tool (LTI 1.3) with the tool URLs from GET /api/admin/lti/endpoints (tool.oidc_login_url, tool.launch_url, jwks_url), then POST /api/admin/lti/platforms with Moodle’s issuer (site URL), client ID, deployment ID, and its auth.php, token.php and certs.php URLs. A resource link picks the course with the custom parameter course_id=<id> (deep linking sets it), ?course=<id> on the target link URI, or the platform’s default_course_id.

  • Per-tenant RSA key set, private keys encrypted with the tenant APP_KEY, rotated monthly (ulams:lti:rotate-keys, scheduled; --init at provisioning, step lti_keys).
  • Login hints, deep-linking data and picker forms are signed with an APP_KEY-derived secret and short-lived; login hints, JWT ids, OIDC state/nonce and launch codes are single use (lti_nonces).
  • Every inbound JWT is checked for signature, iss, aud, exp, nonce/jti and deployment id.
  • Outgoing requests (JWKS, tokens, AGS) go only to public https addresses, without redirects.
  • Tool side: users are matched by (platform, sub), never by e-mail; Instructor maps to tutor, nobody to admin.
  • Every launch is audited in lti_launches.

See src/config.php and the LTI section of docs/enviromental-variables.md.

Terminal window
vendor/bin/phpunit --testsuite lti

Fake tools and platforms sign with in-test RSA keys (tests/Support/KeyPair.php); the tool side’s HTTP client is replaced with an in-memory platform. tests/Feature/TenantIsolationTest.php checks that hints, AGS tokens, deep-linking data and keys of one tenant are rejected by another; the HTTP version against two real tenants is in packages/tenancy/tests/Integration.

  • LtiLink
Method Path Auth Action
GET /api/lti/jwks JwksController
GET /.well-known/jwks.json JwksController
GET /api/lti/frame-origins FrameOriginsController
GET /api/admin/lti/endpoints yes LtiAdminController@endpoints
GET /api/admin/lti/tools yes LtiAdminController@tools
POST /api/admin/lti/tools yes LtiAdminController@storeTool
GET /api/admin/lti/tools/{tool} yes LtiAdminController@showTool
PUT /api/admin/lti/tools/{tool} yes LtiAdminController@updateTool
DELETE /api/admin/lti/tools/{tool} yes LtiAdminController@destroyTool
POST /api/admin/lti/tools/{tool}/deep-link yes LtiAdminController@deepLink
GET /api/admin/lti/platforms yes LtiAdminController@platforms
POST /api/admin/lti/platforms yes LtiAdminController@storePlatform
GET /api/admin/lti/platforms/{platform} yes LtiAdminController@showPlatform
PUT /api/admin/lti/platforms/{platform} yes LtiAdminController@updatePlatform
DELETE /api/admin/lti/platforms/{platform} yes LtiAdminController@destroyPlatform
POST /api/lti/launches/{topic} yes PlatformController@launch
GET|POST /api/lti/platform/authorize PlatformController@oidcAuthorize
POST /api/lti/platform/token PlatformController@token
POST /api/lti/platform/deep-links PlatformController@deepLinks
GET /api/lti/platform/ags/{course}/lineitems AgsController@index
POST /api/lti/platform/ags/{course}/lineitems AgsController@store
GET /api/lti/platform/ags/{course}/lineitems/{lineItem} AgsController@show
PUT /api/lti/platform/ags/{course}/lineitems/{lineItem} AgsController@update
DELETE /api/lti/platform/ags/{course}/lineitems/{lineItem} AgsController@destroy
POST /api/lti/platform/ags/{course}/lineitems/{lineItem}/scores AgsController@scores
GET /api/lti/platform/ags/{course}/lineitems/{lineItem}/results AgsController@results
GET /api/lti/platform/nrps/{course} NrpsController@memberships
GET|POST /api/lti/tool/login ToolController@login
POST /api/lti/tool/launch ToolController@launch
POST /api/lti/tool/launch/verify ToolController@verify
POST /api/lti/tool/deep-link ToolController@deepLink
POST /api/lti/tool/exchange ToolController@exchange
Permission Seeded for roles Constant
lti_manage admin LtiPermissionsEnum::LTI_MANAGE

Registered with AdministrableConfig::registerConfig (editable in the admin panel under Configuration → Settings).

None.

None.

Command Description Signature
ulams:lti:rotate-keys Rotate the tenant LTI signing keys: next becomes active, a new next key is generated, old retired keys are removed ulams:lti:rotate-keys {--init : only create the key set when missing (tenant provisioning)}
Kind Target Frequency
command 'ulams:lti:rotate-keys' monthlyOn(1, '03:00')

LTI_ACCESS_TOKEN_TTL, LTI_ALLOW_INSECURE_URLS, LTI_CODE_TTL, LTI_HTTP_TIMEOUT, LTI_ID_TOKEN_TTL, LTI_ISSUER, LTI_JWKS_CACHE_TTL, LTI_KEY_BITS, LTI_LOGIN_HINT_TTL, LTI_RETIRED_KEY_GRACE_DAYS, LTI_STATE_TTL, LTI_TOOL_LANDING_URL