
Если вы когда-либо пытались построить интеграцию для системы управления обучением (LMS), такой как Canvas, Moodle или Blackboard, вы знаете эту боль. Конкретно, боль LTI 1.3 (Learning Tools Interoperability).
Это сложный танец из OIDC-логинов, наборов ключей JWKS, RSA-подписей и приватных ключей. Задумано это как безопасное решение, но для разработчика, который просто хочет «встроить веб-приложение в Canvas», это может ощущаться как кошмар.
Вот архитектура, к которой я в итоге пришёл: Node.js для слоя безопасности, Next.js для фронтенда, намеренно разделённые, а не собранные в один монолит.
🔗 Репозиторий на GitHub: github.com/paurushrai/lti-1.3-node-nextjs-template
🧐 Что такое LTI 1.3 и почему это важно?
LTI (Learning Tools Interoperability), это стандарт, позволяющий внешним инструментам (таким как ваше приложение) взаимодействовать с LMS (такой как Canvas).
Раньше LTI 1.1 был простым: вы обменивались «секретным ключом» и «ключом потребителя». Но это было небезопасно. LTI 1.3 переходит на современную модель информационной безопасности:
- OIDC (OpenID Connect) для потока логина.
- JWT (JSON Web Tokens) для обмена данными.
- Шифрование с открытым/закрытым ключом (RSA256) вместо общих секретов.
Это делает протокол невероятно безопасным, но и значительно усложняет реализацию с нуля.
🏗 Архитектура
Чтобы решить эту задачу, мы не будем строить монолит. Мы разделим нашего провайдера безопасности и UI приложения.
Технологический стек
- Бэкенд (охранник безопасности): Node.js + Express +
ltijs(стандартная библиотека Node.js для LTI 1.3, от Cvmcosta). - Фронтенд (пользовательский опыт): Next.js 14+ (App Router) + React + Tailwind CSS.
- База данных: PostgreSQL (хранит криптографические ключи и регистрации платформ).
- Инфраструктура: Docker Compose.
Проблема: апокалипсис «сторонних cookie»
Главная сложность с LTI-инструментами в том, что они работают внутри <iframe> внутри LMS. Современные браузеры (Safari, Chrome в режиме инкогнито) по умолчанию блокируют сторонние cookie.
Если ваше веб-приложение полагается на сессионный cookie (connect.sid)? Оно откажет работать в Safari.
Решение: поток токенов «LTIaaS»
Этот проект реализует «stateless»-паттерн с токенами, чтобы обойти ограничение cookie:
- Запуск из LMS: LMS отправляет запрос на наш бэкенд на Node.js.
- Рукопожатие: бэкенд проверяет подпись. Если она валидна, он генерирует короткоживущий, одноразовый токен ltik (LTI key).
- Редирект: бэкенд перенаправляет iframe на фронтенд Next.js, передавая токен в URL:
https://myapp.com/app?ltik=eyJhb... - Гидратация: приложение Next.js забирает этот токен из URL.
- API-запросы: фронтенд использует этот токен как Bearer-токен в заголовке
Authorizationдля всех последующих запросов к API.
Никаких cookie не требуется!
🚀 Как это настроить (пошагово)
Вот как запустить это на своей локальной машине за пять минут.
1. Предварительные требования
Вам понадобится установленный Docker. Также понадобится ngrok (или аналогичный сервис туннелирования), потому что LTI-инструменты обязаны обслуживаться по HTTPS. Просто использовать localhost не получится.
2. Склонируйте репозиторий
git clone https://github.com/paurushrai/lti-1.3-node-nextjs-template.git
cd lti-1.3-node-nextjs-template3. Настройте окружение
Скопируйте файл окружения-пример:
cp lti-server/.env.example lti-server/.envСгенерируйте случайный надёжный ключ и вставьте его в LTI_KEY внутри .env.
4. Запустите туннель
Запустите ngrok на порту 3000 (там живёт наш сервер Node).
ngrok http 3000Скопируйте свой https-адрес (например, https://my-tool.ngrok-free.app).
5. Запуск приложения
Снова откройте lti-server/.env и установите UI_URL в ваш адрес ngrok.
Теперь поднимите всё через Docker:
docker-compose up --buildТеперь у вас есть:
- LTI-сервер:
http://localhost:4000 - Клиент на Next.js:
http://localhost:3000 - Postgres: подключён автоматически.
🔌 Подключение к LMS («волшебная» ссылка)
Раньше приходилось вручную копировать и вставлять публичные ключи и URL. Этот проект поддерживает динамическую регистрацию LTI.
- Скопируйте свой URL регистрации:
https://<your-ngrok-domain>/lti/register - Перейдите в вашу LMS (например, Canvas Admin → Developer Keys).
- Выберите «Dynamic Registration».
- Вставьте URL и нажмите Register.
Готово. 💥 Инструмент и LMS автоматически обмениваются публичными ключами. Инструмент теперь установлен.
🧪 Тестирование потока
- Создайте обычное задание или элемент модуля в вашем курсе LMS.
- Выберите «External Tool» и укажите ваш новый инструмент.
- Запустите его.
- Вы увидите, как внутри iframe загружается приложение Next.js.
- Оно покажет: «Welcome, [Ваше имя]» и вашу роль (Instructor/Student).
Приложение успешно:
- Аутентифицировало запуск.
- Декодировало ваш профиль пользователя из данных запуска LTI.
- Безопасно передало его на фронтенд.
Разделили бы вы слой безопасности и UI так же?
Отделение логики протокола (Node/ltijs) от логики приложения (Next.js), это то, что сделало задачу решаемой: слой безопасности остаётся скучным и легко проверяемым, а UI остаётся обычным приложением на Next.js, которому не нужно ничего знать про OIDC или JWKS.
Код лежит на GitHub, если хотите форкнуть его и начать с того, что уже обрабатывает рукопожатие: lti-1.3-node-nextjs-template. Интересно, сталкивался ли кто-то со случаем, когда такое разделение двух слоёв мешало, а не помогало.


