LTI Integration for Your LMS Platform

LTI integration often breaks due to version mismatches, session loss, or misconfigured JWKS. Our engineers solve these issues end-to-end—from auditing your current LMS to connecting any LTI-compliant tool. We have accumulated a library of typical errors and ready templates, significantly shortening

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1288
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    986
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1038
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1112
  • image_website-_0.webp
    Website development for Red Pear
    556

LTI integration often breaks due to version mismatches, session loss, or misconfigured JWKS. Our engineers solve these issues end-to-end—from auditing your current LMS to connecting any LTI-compliant tool. We have accumulated a library of typical errors and ready templates, significantly shortening development time.

According to the IMS Global LTI 1.3 specification, using OAuth 2.0 and JWKS provides a higher security level compared to LTI 1.1.

Why LTI 1.3 is Better Than LTI 1.1?

LTI 1.1 uses OAuth 1.0—an outdated protocol with fragile signatures. LTI 1.3 is based on OAuth 2.0 and OpenID Connect: more secure, transparent, and easier to debug. JWT tokens with JWKS keys prevent request forgery. Many vendors (Coursera, Zoom, Adobe) are moving to LTI 1.3—without it, you cannot connect modern tools.

Feature LTI 1.1 LTI 1.3
Protocol OAuth 1.0 OAuth 2.0 + OIDC
Signature HMAC-SHA1 RSA/JWT
Key management Shared secret (static) JWKS (rotation)
Security Low High
Integration time 2-3 days 3-5 days

How We Configure LTI 1.3 in Your LMS

The LMS acts as the Platform (per IMS). We initialize the process using OIDC Login Request and generate JWT. Below is typical code with the ltijs library:

import { Provider } from 'ltijs'; // npm install ltijs // Configure LMS as LTI Platform Provider.setup( process.env.LTI_ENCRYPTION_KEY!, // 32+ characters for cookie encryption { url: process.env.DATABASE_URL!, plugin: require('ltijs-postgresql'), // adapter for PostgreSQL }, { cookies: { secure: true, sameSite: 'None' }, devMode: process.env.NODE_ENV !== 'production', } ); // Register external tool await Provider.registerPlatform({ url: 'https://tool.example.com', name: 'Kahoot Integration', clientId: process.env.KAHOOT_CLIENT_ID!, authenticationEndpoint: 'https://tool.example.com/lti/auth', accesstokenEndpoint: 'https://tool.example.com/lti/token', authConfig: { method: 'JWK_SET', key: 'https://tool.example.com/.well-known/jwks.json', }, }); // Tool launch handler Provider.onConnect(async (token, req, res) => { const { email, name, role } = token.userInfo; const contextId = token.platformContext.context?.id; // Verify/create user in external tool res.json({ token: token.jwt }); }); await Provider.deploy({ serverless: true }); 

LTI 1.1—for Legacy Tools

Some vendors (Phet, certain tests) still use LTI 1.1 with OAuth 1.0 signature:

import oauth from 'oauth-signature'; function launchLti11( launchUrl: string, consumerKey: string, consumerSecret: string, params: Record<string, string> ): { url: string; method: 'POST'; body: string } { const baseParams: Record<string, string> = { lti_message_type: 'basic-lti-launch-request', lti_version: 'LTI-1p0', oauth_callback: 'about:blank', oauth_consumer_key: consumerKey, oauth_nonce: crypto.randomUUID().replace(/-/g, ''), oauth_signature_method: 'HMAC-SHA1', oauth_timestamp: String(Math.floor(Date.now() / 1000)), oauth_version: '1.0', ...params, }; const signature = oauth.generate('POST', launchUrl, baseParams, consumerSecret, ''); baseParams.oauth_signature = signature; const body = new URLSearchParams(baseParams).toString(); return { url: launchUrl, method: 'POST', body }; } // LTI tool launch page app.get('/courses/:courseId/tools/:toolId/launch', authenticate, async (req, res) => { const tool = await db.ltiTools.findById(req.params.toolId); const enrollment = await db.enrollments.findByCourseAndUser( req.params.courseId, req.user.id ); if (tool.version === '1.1') { const launch = launchLti11(tool.launch_url, tool.consumer_key, tool.consumer_secret, { resource_link_id: `${req.params.courseId}-${req.params.toolId}`, resource_link_title: tool.name, user_id: req.user.id, lis_person_name_full: req.user.name, lis_person_contact_email_primary: req.user.email, roles: enrollment.role === 'instructor' ? 'Instructor' : 'Student', context_id: req.params.courseId, context_title: enrollment.courseTitle, }); // Auto-submit form via HTML res.send(` <!DOCTYPE html> <html> <body> <form id="lti" method="POST" action="${launch.url}"> ${Object.entries(Object.fromEntries(new URLSearchParams(launch.body))) .map(([k, v]) => `<input type="hidden" name="${k}" value="${v}" />`) .join('\n')} </form> <script>document.getElementById('lti').submit();</script> </body> </html> `); } }); 

Storing Tool Configuration

CREATE TABLE lti_tools ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), course_id UUID REFERENCES courses(id), name VARCHAR(255) NOT NULL, version VARCHAR(10) NOT NULL, -- '1.1' | '1.3' -- LTI 1.1 launch_url TEXT, consumer_key VARCHAR(255), consumer_secret TEXT, -- LTI 1.3 client_id VARCHAR(255), platform_id VARCHAR(255), deployment_id VARCHAR(255), oidc_auth_url TEXT, jwks_url TEXT, -- Settings open_in_new_tab BOOLEAN DEFAULT false, custom_params JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT now() ); 

Getting Results via LTI Outcomes (1.1)

// Tool can send score back to LMS app.post('/lti/grade', async (req, res) => { const { sourcedId, score, action } = parseLtiOutcomesXml(req.body); // sourcedId contains userId and resourceLinkId const [userId, resourceId] = parseLisResultSourcedId(sourcedId); await db.ltiGrades.upsert({ userId, resourceId, score: Number(score), receivedAt: new Date(), }); // Update course progress await updateCourseProgress(userId, resourceId, Number(score)); res.type('application/xml').send(` <?xml version="1.0" encoding="UTF-8"?> <imsx_POXEnvelopeResponse xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"> <imsx_POXHeader> <imsx_POXResponseHeaderInfo> <imsx_version>V1.0</imsx_version> <imsx_messageIdentifier>${crypto.randomUUID()}</imsx_messageIdentifier> <imsx_statusInfo> <imsx_codeMajor>success</imsx_codeMajor> <imsx_severity>status</imsx_severity> </imsx_statusInfo> </imsx_POXResponseHeaderInfo> </imsx_POXHeader> <imsx_POXBody><replaceResultResponse /></imsx_POXBody> </imsx_POXEnvelopeResponse> `); }); 

What's Included in LTI Integration Work?

  • Audit of current LMS architecture: identifying bottlenecks (N+1 queries, session loss), evaluating compatibility with required tools.
  • Designing storage schema for tool configurations (lti_tools table with support for both LTI versions).
  • Implementing LTI 1.1 and/or LTI 1.3 launch flow on your stack (Node.js, Python, PHP, Ruby—any).
  • Integration with external tool: registration, test launch, grade passback (Outcomes).
  • Writing documentation for administrators and instructors on adding new tools.
  • Training your team: how to debug errors, update keys, work with JWKS.
  • 30-day post-release support: bug fixes, consultations.
Common Mistakes in LTI Integration
  • User type mismatch: confusing Instructor and Student roles in JWT.
  • JWKS errors: keys expire or are not updated.
  • CORS issues: external tools block requests.
  • Incorrect OIDC Login Request configuration: wrong redirect URI.

Timelines and Cost

LTI 1.1 integration (Consumer + Launch + Grades) — 3–5 days. LTI 1.3 with OIDC flow — 1 week. Support for both versions plus tool management UI — 2 weeks. Cost is determined individually, and using ready templates can significantly reduce the budget.

Our Experience and Guarantees

Case: integrating Kahoot into a university LMS. It required LTI 1.3 support, as Kahoot had moved to the new standard. We implemented the OIDC flow, configured JWKS, and tested grade passback. As a result, students got a unified interface without repeated authentication. Get a consultation on LTI integration today. Contact us – we will assess your task and offer a solution within 2 business days.