If you've ever tried to integrate with a modern LMS such as Canvas, Moodle, or Schoology, you know the pain. The LTI 1.3 specification is powerful but technically complex. It involves OAuth 2.0, OIDC, JWTs, platform-specific quirks, and documentation scattered across various locations.
I needed to build an LMS integration for a side project. When I went looking for a modern TypeScript library, I found... nothing production-ready. The options were either not serverless-friendly, incomplete, or abandoned.
So I built one.
What is LTI?
Learning Tools Interoperability (LTI) is the standard that connects external tools to learning management systems (LMS). It's what lets platforms like Kahoot, Turnitin, and thousands of other tools appear seamlessly inside Canvas or Moodle.
The LMS market is worth over $20 billion. Every major platform uses LTI: Canvas, Moodle, Blackboard, Brightspace, and Schoology. If you're building anything for education, chances are you'll eventually need it.
LTI 1.3 is the current version, built on OAuth 2.0 and OpenID Connect. It's more secure than the old OAuth 1.0-based LTI 1.1, but also significantly more complex to implement.
What I Built
LTI Tool is a TypeScript-native implementation of the full IMS Global LTI 1.3 specification. Here's the core setup:
import { Hono } from 'hono';
import { LTITool } from '@lti-tool/core';
import {
jwksRouteHandler,
launchRouteHandler,
loginRouteHandler,
secureLTISession,
} from '@lti-tool/hono';
import { MemoryStorage } from '@lti-tool/memory';
const storage = new MemoryStorage();
const ltiTool = new LTITool({
keyPair, // RSA keypair - see docs for generation
stateSecret, // HMAC secret for state tokens
storage,
});
const app = new Hono();
// Mount required LTI endpoints
app.get('/lti/jwks', jwksRouteHandler(ltiTool));
app.post('/lti/login', loginRouteHandler(ltiTool));
app.post('/lti/launch', launchRouteHandler(ltiTool));
// Protect your routes
app.use('/app/*', secureLTISession(ltiTool));
And here's a protected route that accesses the LTI session:
app.get('/app/dashboard', (c) => {
const session = c.get('ltiSession');
return c.json({
user: session.user.name,
course: session.context?.title,
roles: session.roles,
});
});
That's it. The library handles OIDC authentication, JWT verification, nonce validation, and all the security requirements.
Full Feature Set
- ✅ Complete OIDC authentication flow
- ✅ Cookieless state management (works in iframes, no third-party cookie issues)
- ✅ Assignment and Grade Services (AGS) - submit grades, manage line items
- ✅ Names and Role Provisioning Services (NRPS) - access course rosters
- ✅ Deep Linking - content selection with multiple placement types
- ✅ Dynamic Registration - zero-config tool setup
- ✅ TypeScript-first with complete type safety
- ✅ Serverless-optimized (AWS Lambda, Cloudflare Workers)
- ✅ Pluggable storage (DynamoDB, Memory, MySQL, bring your own)
The Quirks I Discovered
Building against a specification is one thing. Building against real-world platforms is another. Every LMS has quirks, parameters in unexpected places, undocumented behaviors, or subtle differences in how they interpret the spec. The library handles these so you don't have to discover them yourself.
Canvas, for example, announced that starting January 2026, they'll reject API requests without proper User-Agent headers. The library sends @lti-tool/core/1.0.0 on all service requests by default, so you're compliant out of the box.
The Security Details That Matter
LTI 1.3 security isn't optional. The library enforces:
- JWT signature verification against platform public keys
- Nonce validation to prevent replay attacks
- State parameter verification for CSRF protection
- Token expiration checks
Getting any of these wrong means your integration is insecure. Getting them right means implementing them once, correctly, and never thinking about them again.
Real-World Testing
This isn't a spec-compliant library that's never seen production. I've
tested it against live Moodle and Canvas instances.
The test suite has 50+ tests covering the core flows. But real validation came from watching grades actually appear in the LMS gradebook.
Try It
npm install @lti-tool/core @lti-tool/hono @lti-tool/memory
The library is MIT licensed. If you're building LMS integrations in Node.js, this should save you weeks of implementation time.
Links
If you find it useful, a star on GitHub helps others discover it.
If you run into issues or have questions, open an issue.
Building something with LTI Tool? I'd love to hear about it.
Top comments (0)