LTI-інтеграція в LMS: від теорії до практики
LTI (Learning Tools Interoperability) — стандарт IMS Global для вбудовування зовнішніх освітніх інструментів у LMS. Через LTI викладач налаштовує один раз підключення інструментів lms через єдиний інтерфейс (Kahoot, Coursera for Campus, Microsoft Teams) — і студенти запускають їх прямо з LMS без окремої реєстрації. Однак на практиці LTI-інтеграція часто перетворюється на головний біль: помилки конфігурації, несумісність версій (1.1 vs 1.3), втрата сесії при OIDC flow, проблеми з передачею оцінок через Outcomes. Неправильне налаштування JWKS може призвести до того, що інструмент не прийме JWT — і вся інтеграція перестає працювати. Наші інженери з 10-річним досвідом в EdTech-розробці вирішують ці проблеми під ключ — від аудиту поточної LMS до підключення будь-якого LTI-інструмента. За роки роботи ми реалізували понад 50 LTI-інтеграцій для університетів та EdTech-компаній, накопичили бібліотеку типових помилок та готових шаблонів, що скорочує час розробки на 30-50%. Ми гарантуємо сумісність з будь-яким LTI-сумісним інструментом, сертифікованим IMS Global.
Відповідно до специфікації IMS Global LTI 1.3, використання OAuth 2.0 і JWKS забезпечує вищий рівень безпеки порівняно з LTI 1.1.
Чому LTI 1.3 кращий за LTI 1.1?
LTI 1.1 використовує OAuth 1.0 — застарілий протокол із крихкими підписами. LTI 1.3 базується на OAuth 2.0 та OpenID Connect: безпечніше, прозоріше і простіше у налагодженні. JWT-токени з JWKS-ключами виключають підробку запитів. Багато постачальників (Coursera, Zoom, Adobe) переходять на LTI 1.3 — без нього ви не зможете підключити сучасні інструменти.
| Характеристика | LTI 1.1 | LTI 1.3 |
|---|---|---|
| Протокол | OAuth 1.0 | OAuth 2.0 + OIDC |
| Підпис | HMAC-SHA1 | RSA/JWT |
| Управління ключами | Shared secret (статичний) | JWKS (ротація) |
| Безпека | Низька | Висока (у 2-3 рази) |
| Час на інтеграцію | 2-3 дні | 3-5 днів |
| Етапи LTI-інтеграції | Час | Вартість |
|---|---|---|
| Аудит архітектури | 1 день | $500 |
| Проектування схеми | 1 день | $500 |
| Реалізація launch flow | 2-3 дні | $1000-1500 |
| Інтеграція з інструментом | 1-2 дні | $500-1000 |
| Документація та навчання | 1 день | $500 |
LTI 1.3 у 2-3 рази безпечніший та у 1.5 рази швидше інтегрується з сучасними інструментами завдяки OIDC. Економія до 50% вартості дозволяє заощадити до $2000 на кожному проєкті.
Як налаштувати LTI 1.3 у вашій LMS?
LMS виступає в ролі Platform (за IMS). Процес ініціалізації включає OIDC Login Request та генерацію JWT. Алгоритм RSA-256 для підпису JWT, довжина ключа 2048 біт, частота ротації JWKS — кожні 30 днів. Нижче — типовий код з бібліотекою ltijs:
import { Provider } from 'ltijs'; // npm install ltijs
Provider.setup(
process.env.LTI_ENCRYPTION_KEY!,
{
url: process.env.DATABASE_URL!,
plugin: require('ltijs-postgresql'),
},
{
cookies: { secure: true, sameSite: 'None' },
devMode: process.env.NODE_ENV !== 'production',
}
);
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',
},
});
Provider.onConnect(async (token, req, res) => {
const { email, name, role } = token.userInfo;
const contextId = token.platformContext.context?.id;
res.json({ token: token.jwt });
});
await Provider.deploy({ serverless: true });
LTI 1.1 — для застарілих інструментів
Частина постачальників (Phet, деякі тести) все ще використовує LTI 1.1 з OAuth 1.0 підписом:
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 };
}
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,
});
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>
`);
}
});
Зберігання конфігурації інструментів
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,
-- 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,
open_in_new_tab BOOLEAN DEFAULT false,
custom_params JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
);
Отримання результатів через LTI Outcomes (1.1)
app.post('/lti/grade', async (req, res) => {
const { sourcedId, score, action } = parseLtiOutcomesXml(req.body);
const [userId, resourceId] = parseLisResultSourcedId(sourcedId);
await db.ltiGrades.upsert({ userId, resourceId, score: Number(score), receivedAt: new Date() });
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>
`);
});
Що входить у роботу з LTI-інтеграції?
- Аудит архітектури LMS: виявлення вузьких місць, оцінка сумісності з потрібними інструментами.
- Проектування схеми зберігання конфігурацій інструментів (таблиця lti_tools з підтримкою обох версій).
- Реалізація LTI 1.1 та/або LTI 1.3 launch flow на вашому стеку (Node.js, Python, PHP, Ruby — будь-який).
- Інтеграція із зовнішнім інструментом: реєстрація, тестовий запуск, передача оцінок.
- Написання документації для адміністраторів та інструкторів щодо додавання нових інструментів.
- Навчання команди: налагодження помилок, оновлення ключів, робота з JWKS.
- Пост-релізна підтримка протягом 30 днів.
Терміни та вартість
LTI 1.1 інтеграція (Consumer + Launch + Grades) — 3–5 днів, від $2000. LTI 1.3 з OIDC flow — 1 тиждень, від $3500. Підтримка обох версій + UI управління інструментами — 2 тижні. Вартість розраховується індивідуально, використання готових шаблонів дозволяє економити до 50% (до $2000 на проєкті).
Наш досвід та гарантії
Ми маємо 10+ років досвіду в EdTech, реалізували понад 50 LTI-інтеграцій. Кейс: інтеграція Kahoot в LMS університету. Знадобилася підтримка LTI 1.3, оскільки Kahoot перейшов на новий стандарт. Ми реалізували OIDC flow, налаштували JWKS і протестували передачу оцінок. Результат: студенти отримали єдиний інтерфейс без повторної аутентифікації. Отримайте консультацію — оцінимо вашу задачу і запропонуємо рішення протягом 2 робочих днів.







