BLOG · LMS· LTI
LTI 1.3 ile LMS'e dış araç entegrasyonu: launch, Deep Linking ve AGS ile not geri yazma
Efe Müderrisoğlu · 1 Ağustos 2026 · 14 dakika
Bir LMS’e dış araç bağlama işi, ilk bakışta “aracın adresini alıp iframe’e koy” kadar basit görünür. Gerçekte iş, kimin kim olduğunu, hangi dersin içinde bulunulduğunu ve notun nereye yazılacağını iki ayrı sistemin imzalı biçimde anlaşarak çözmesidir. LTI 1.3 bu anlaşmanın standardıdır ve en çok vakit kaybettiren kısmı protokolün kendisi değil, hangi tarafta olduğunuzu baştan yanlış belirlemektir.
LTI 1.3’te tool tarafı ile platform tarafı arasındaki fark nedir?
Platform, aracı içine gömen taraftır: LMS. Tool, gömülen dış üründür: Turnitin, bir video platformu, bir etkileşimli içerik aracı. İki taraf aynı protokolü konuşur ama akıştaki rolleri simetrik değildir, dolayısıyla yazacağınız kod da kütüphane seçiminiz de birbirinden tamamen farklıdır.
Kendi LMS’ini yazan ekiplerin en yaygın hatası burada başlar. Python
ekosisteminde arama yapınca ilk çıkan pylti1p3, adı gereği “LTI 1.3” dese de
tool tarafını uygular: gelen bir id_token‘ı doğrulamak, platformun JWKS’ini
okumak, aracın kendi oturumunu kurmak için tasarlanmıştır. LMS tarafında ise
tam tersi lazımdır: id_token‘ı siz üretir ve imzalarsınız, JWKS’i siz yayımlarsınız, aracı siz çağırırsınız.
Biz bu hatayı yaptık. Tasarım belgesinde bağımlılık olarak pylti1p3 yazılıydı,
implementasyona geçince akışın yönünün ters olduğu ortaya çıktı ve platform
tarafını uygulayan lti1p3platform kütüphanesine geçildi. Kaybedilen zaman,
protokolü öğrenmenin değil, yönü baştan sormamanın bedeliydi.
| Platform (LMS) | Tool (dış araç) | |
|---|---|---|
id_token‘ı | üretir ve imzalar | doğrular |
| JWKS’i | yayımlar | okur |
| OIDC login init’i | başlatır (aracın login ucuna gider) | karşılar |
| Deep Linking’de | istek gönderir, yanıtı doğrular | seçim ekranını gösterir |
| AGS’de | line item ve score ucunu sunar | notu yazar |
| Örnek Python kütüphanesi | lti1p3platform | pylti1p3 |
Karar tek soruyla verilir: gömen mi, gömülen mi? LMS yazıyorsanız gömensiniz.
LTI 1.3 launch akışı adım adım nasıl işler?
Launch, üç aşamalı bir OIDC akışıdır: platform bir preflight isteğiyle aracı
uyarır, araç kendi kimlik doğrulama isteğiyle geri döner, platform imzalı bir id_token üretip aracın redirect_uri‘sine POST eder. Aracın gördüğü tek
otoriter kaynak bu imzalı jetondur.
Öğrenci ders içindeki araç kalemine tıklar
→ POST /lti/{tenant}/login (3rd-party init: iss, login_hint,
target_link_uri, lti_message_hint)
→ araç kendi auth request'ini döner (state, nonce, redirect_uri, client_id)
→ platform id_token'ı RS256 ile imzalar, redirect_uri'ye POST eder
→ araç, platformun /lti/{tenant}/jwks ucundan public key'i çeker,
imzayı ve iss / aud / exp / nonce alanlarını doğrular
→ araç açılır (iframe veya yeni sekme) nonce ve state tek kullanımlıktır. Kullanılan nonce’ları kısa ömürlü bir
tabloda tutup tekrar geldiğinde reddetmezseniz, yakalanmış bir launch isteği
tekrar oynatılabilir. Bizde bu lti_nonces tablosu ve süresi geçenleri temizleyen
bir zamanlanmış iş olarak duruyor.
Anahtar çifti ve kid meselesi
Platform her kiracı için bir RSA anahtar çifti üretir; private key veritabanında
şifreli durur, public key JWKS ucundan yayımlanır. Buradaki en sinsi ayrıntı kid değeridir. Kütüphanelerin bir kısmı kid‘i private key’in JWK
thumbprint’inden türetir; siz ise JWKS’te kendi ürettiğiniz kimliği yayımlarsınız.
İkisi eşleşmezse araç imzayı doğrulayacak anahtarı bulamaz ve hata mesajı
“invalid signature” olur, oysa imza doğrudur.
Çözüm, kid üretimini tek noktaya sabitlemektir:
class _PlatformRegistration(Registration):
"""get_kid()'i JWKS'te yayımladığımız kid'e sabitler."""
_kid_override = None
def set_kid(self, kid: str):
self._kid_override = kid
return self
def get_kid(self):
return self._kid_override or super().get_kid() Yayımlanan anahtarın kimliği ile imzalanan jetonun başlığındaki kimlik birebir aynı olmalıdır. Bu tek satırlık kural, entegrasyonda saatler kazandırır.
Deep Linking ders içine içerik gömmeyi nasıl değiştirir?
Deep Linking, eğitmenin aracın kendi arayüzünde içerik seçmesini ve seçimin imzalı bir yanıtla LMS’e dönmesini standartlaştırır. Onsuz, derse yalnız aracın kök adresini bağlayabilirsiniz; eğitmen “şu videoyu” ya da “şu ödev kutusunu” seçemez.
Akış, launch ile aynı OIDC iskeletini kullanır, farkı mesaj tipindedir:
Eğitmen "araç ekle" der
→ login init, lti_message_hint=deeplink
→ araç LtiDeepLinkingRequest mesajıyla iframe'de açılır
→ eğitmen içeriği seçer
→ araç LtiDeepLinkingResponse JWT'sini platformun deeplink ucuna POST eder
→ platform JWT'yi doğrular, content_items dizisini ayrıştırır
→ her kalem için bir kaynak bağlantısı (resource link) kaydı üretilir Yanıtın içinde gelen content_items kabaca şuna benzer:
{
"type": "ltiResourceLink",
"title": "Ünite 3 videosu",
"url": "https://arac.example.com/launch/vid-8812",
"custom": { "video_id": "8812" }
} Bu kalem sizde kalıcı bir satıra dönüşür. Öğrenci launch’ı artık aracın kök
ekranını değil, doğrudan o kaynağı hedefler. custom alanı aracın kendi
bağlamını taşır; siz içine bakmazsınız, olduğu gibi geri gönderirsiniz.
NRPS ne zaman gerekir?
Launch tek bir kullanıcı hakkında bilgi taşır. Araç sınıfın tamamını görmek istiyorsa Names and Role Provisioning Services’i çağırır ve platformun sunduğu üyelik ucundan ders bağlamındaki kullanıcı listesini rolleriyle birlikte çeker.
Bu, sandığınızdan daha çok araçta gerekir. Grup kuran, akran değerlendirmesi yapan, ödev teslimlerini sınıf bazında listeleyen her araç NRPS ister. Platform tarafında bunun anlamı iki şeydir: bir üyelik ucu yayımlamak ve kurumsal rollerinizi standart LTI rol URI’lerine eşlemek.
| CardeaCore rolü | LTI rolü |
|---|---|
| Öğrenci | ...#Learner |
| Öğretim üyesi | ...#Instructor |
| Asistan | ...#TeachingAssistant |
| Bölüm/dekanlık gözlemcisi | ...#Mentor veya bağlam dışı bırakılır |
Eşleme tablosunu geçiştirmeyin. Aracın öğrenciye eğitmen ekranı açmasının en
yaygın sebebi, kurumdaki “koordinatör” gibi bir rolün gelişigüzel Instructor‘a
eşlenmesidir.
AGS ile not geri yazma nasıl kurulur?
Assignment and Grade Services, aracın ürettiği notu platformun not defterine yazmasını sağlar. Yön önemlidir: notu araç yazar, platform kabul eder. Platform tarafında yapılacak iş, bir OAuth 2 token ucu açmak ve line item ile score uçlarını yayımlamaktır.
Akış şöyledir:
- Launch
id_token‘ı içinde AGS endpoint claim’i taşır: line item adresi ve izin verilen kapsamlar (scope). - Araç, kendi private key’iyle imzaladığı bir JWT’yi
client_credentialsakışında platformun token ucuna sunar. - Platform, aracın JWKS’inden imzayı doğrular ve dar kapsamlı bir erişim jetonu döner.
- Araç bu jetonla ilgili line item’a bir score POST eder.
POST /lti/{tenant}/ags/lineitems/42/scores
Authorization: Bearer <erisim_jetonu>
Content-Type: application/vnd.ims.lis.v1.score+json
{
"userId": "9f31...",
"scoreGiven": 78,
"scoreMaximum": 100,
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"timestamp": "2026-08-01T09:12:00Z"
} Dikkat edilecek üç nokta var. Birincisi gradingProgress: araç PendingManual gönderdiğinde not defterine düşen değer nihai değildir, arayüzde bunu ayırt
etmeniz gerekir. İkincisi timestamp: aynı kullanıcı için daha eski zaman
damgalı bir score geldiğinde reddedilmelidir, yoksa geç ulaşan bir yeniden
deneme yeni notu ezer. Üçüncüsü kapsam denetimi: erişim jetonu yalnız kendi
aracının line item’larına yazabilmelidir, aksi halde bir araç diğerinin notunu
değiştirebilir.
Turnitin gibi benzerlik araçları bu akışa nasıl oturur?
Turnitin, standart bir LTI aracı olarak bağlanır. Deep Linking ile ödev kutusu derse gömülür, öğrenci teslimini aracın ekranında yapar, araç benzerlik raporunu üretir ve puanı AGS üzerinden not defterine yazar. Platform tarafında ek bir “intihal modülü” yazmazsınız; yazdığınız şey, herhangi bir araç için zaten yazdığınız AGS alıcısıdır.
Bu, LTI’ın esas kazancıdır. Bir kez platform tarafını doğru kurduğunuzda, ikinci ve üçüncü aracı bağlamak yalnızca kayıt formunu doldurmaktan ibarettir.
Kendi benzerlik motorunuzu yazmak neden yanlış?
Çünkü benzerlik tespiti bir algoritma problemi değil, bir kaynak erişimi problemidir. Shingling, MinHash, ters indeks: bunların hepsi bilinen ve birkaç haftada yazılabilen tekniklerdir. Zor olan, karşılaştıracağınız metin havuzudur.
Ciddi bir benzerlik hizmeti üç şeye yaslanır: yayıncılarla lisans anlaşmaları altındaki akademik yayın arşivi, sürekli taranan ve güncellenen web indeksi ve kurumlar arası öğrenci ödev havuzu. Üçünün de tek ortak özelliği, yazılım geliştirerek elde edilememesidir. Kendi motorunuzu yazdığınızda kurumun eline geçen şey, yalnız kendi veritabanınızdaki ödevlere bakabilen ve dışarıdan kopyalanmış her metni temiz gösteren bir rapordur. Bu, yokluğundan daha tehlikelidir, çünkü kuruma yanlış bir güven verir.
Bir de sorumluluk boyutu var. Bir öğrenciye intihal isnadı disiplin sürecine girer. O raporun arkasında durabilecek metodoloji, itiraz mekanizması ve kurumsal sorumluluk, bu işi yapan hizmet sağlayıcının işidir. Kampüs platformunun işi, raporun derse doğru bağlanması ve notun doğru öğrenciye yazılmasıdır.
Bir aracı bağlarken hangi bilgiler el değiştirir?
LTI kaydı iki yönlü bir bilgi alışverişidir ve entegrasyonların en çok takıldığı yer, hangi tarafın hangi değeri ürettiğinin karışmasıdır. Kayıt formunu açmadan önce aşağıdaki tabloyu doldurun; iki tarafın da elinde eksik alan kalmadığında entegrasyon genelde ilk denemede çalışır.
| Bilgi | Kim üretir | Kime verilir |
|---|---|---|
Issuer (iss) | Platform | Araca |
| Platform JWKS adresi | Platform | Araca |
| OIDC login başlangıç adresi | Araç | Platforma |
Yönlendirme adresi (redirect_uri) | Araç | Platforma |
| Araç JWKS adresi | Araç | Platforma |
client_id | Platform kaydı sırasında belirlenir | Araca |
deployment_id | Platform | Araca |
| Deep Linking adresi | Araç | Platforma |
| AGS token ucu | Platform | Araca |
client_id ve deployment_id gizli değildir; private key gizlidir. Bu ayrımı
netleştirmek, kurumun bilgi işlem birimiyle yapılan yazışmayı kısaltır. Bir
kurumda client_id “gizli anahtar” sanıldığı için haftalarca paylaşılmayan bir
entegrasyon görmüştük.
Kayıt tarafında bir de test aracı bulundurmak işe yarar. Yeni bir kurum bağlanırken önce bilinen ve çalıştığı doğrulanmış bir referans araca launch denemesi yapmak, sorunun kendi kaydınızda mı yoksa karşı tarafta mı olduğunu tek adımda söyler.
Hata mesajları neyi işaret ediyor?
LTI hataları yanıltıcı isimler taşır. Sık görülen dört tanesinin gerçek karşılığı şudur.
invalid signature: çoğu zaman imza değil, kid uyuşmazlığıdır. Araç
JWKS’inizde jetondaki kid‘i bulamamıştır ve elindeki tek anahtarla deneyip
başarısız olmuştur.
invalid aud: jetondaki aud alanı, aracın kendi client_id‘siyle
eşleşmiyordur. Birden çok kiracı için tek kayıt kullanıldığında tipik olarak
buraya düşülür.
nonce already used: ya gerçekten tekrar oynatma vardır ya da kullanıcı sayfayı
yenilemiştir. İkincisini ayırt etmek için nonce kaydına zaman damgası koyun;
saniyeler içindeki tekrar genelde yenilemedir, dakikalar sonraki değildir.
state mismatch: neredeyse her zaman çerez sorunudur. iframe içinde üçüncü taraf
çerez engellenmiştir ve platform kendi oturumunu bulamamıştır.
Uygulamada dikkat edilecek noktalar
Çok kiracılı bir platformda LTI kaydı kiracı seviyesinde tutulmalıdır. Her
kiracının ayrı client_id, ayrı deployment_id ve ayrı anahtar çifti olur; iss değeri kiracıya göre ayrışır. Tek bir kayıt üzerinden birden çok
üniversiteye hizmet verirseniz, bir kurumun aracı diğerinin bağlamına launch
edebilir.
iframe konusunda erken karar verin. Araçların bir kısmı çerçevelenmeyi reddeder,
bir kısmı üçüncü taraf çerez kısıtı yüzünden iframe içinde oturum kuramaz. Araç
kaydında iframe ile window arasında bir seçim alanı bırakmak, her yeni araçta
kod değiştirmekten ucuzdur.
Test tarafında sahte bir araç yazmak en verimli yatırımdır. Kendi anahtar
çiftiyle imza atan, id_token üreten ve Deep Linking yanıtı POST eden yüz satır
kadar bir test aracı; geçersiz aud, süresi geçmiş exp, tekrarlanmış nonce ve bozuk imza senaryolarını gerçek bir sağlayıcıya bağımlı kalmadan
doğrulamanızı sağlar.
CardeaCore bu işi nasıl yapıyor
CardeaCore LMS’i platform tarafındadır. Kiracı yöneticisi araçları arayüzden kaydeder; kayıt ekranı, aracın istediği platform bilgilerini (issuer, JWKS adresi, login ve launch geri dönüş adresleri, anahtar kimliği) kopyalanabilir biçimde gösterir. Anahtar çifti kiracı başına üretilir, private key şifreli saklanır, public key kiracıya özel JWKS ucundan yayımlanır.
Eğitmen ders yönetim ekranında kayıtlı bir aracı seçtiğinde, araç Deep Linking destekliyorsa seçim ekranı iframe içinde açılır ve dönen kalemler ders içeriğine kaynak bağlantısı olarak eklenir. Öğrenci tarafında kalem, araç kaydındaki hedefe göre iframe ya da yeni sekmede açılır.
Not geri yazma ve NRPS ikinci dilimde duruyor; benzerlik tarafı da tam olarak oraya bağlı. Turnitin’i kendi motorumuzla değiştirmeye çalışmak yerine, AGS alıcısını doğru yazmayı ve aracı standart yoldan bağlamayı tercih ettik.
SIK SORULAN SORULAR
Sık sorulan sorular
LTI 1.3 nedir, LTI 1.1'den farkı ne?
LTI 1.3, bir LMS ile dış öğrenme aracı arasındaki kimlik ve içerik alışverişini OAuth 2 ve OpenID Connect üzerine oturtan bir standarttır. LTI 1.1'in paylaşılan gizli anahtarla OAuth 1.0a imzası yerine asimetrik RSA imzalı JWT kullanır. Pratikte farkı, gizli anahtarın iki tarafta da tutulmasına gerek kalmaması ve anahtarların JWKS uçlarından döndürülebilmesidir.
LTI'da tool tarafı ile platform tarafı arasındaki fark nedir?
Platform, aracı kendi içine gömen taraftır, yani LMS. Tool, gömülen dış üründür, örneğin Turnitin veya bir video aracı. Aynı akışın iki ucudur ve kütüphaneler genelde yalnız bir ucu uygular. Kendi LMS'inizi yazıyorsanız platform tarafındasınız.
Deep Linking olmadan LTI aracı derse eklenebilir mi?
Eklenebilir. Aracın launch adresini elle girip sabit bir kaynak bağlantısı oluşturabilirsiniz. Ancak eğitmen aracın içindeki belirli bir içeriği seçemez, her açılışta aracın kök ekranına düşer. Deep Linking tam olarak bu seçim adımını standartlaştırır.
AGS ile not geri yazmak için neye ihtiyaç var?
Platformun bir OAuth 2 token ucu sunması, aracın client credentials akışıyla erişim jetonu alması ve line item ile score uçlarının yayımlanması gerekir. Launch id_token'ı içindeki endpoint claim'i araca bu adresleri bildirir. Not, araçtan platforma yazılır, ters yönde değil.
Kendi intihal veya benzerlik motorumuzu yazmalı mıyız?
Hayır. Benzerlik tespiti sürekli güncellenen bir kaynak indeksine, yayıncı anlaşmalarına ve öğrenci ödev arşivine dayanır. Bunların hiçbiri yazılım problemi değil, veri ve lisans problemidir. Doğru yol, Turnitin gibi bir aracı LTI ile bağlamak ve notu AGS üzerinden geri almaktır.
NRPS ne işe yarar?
Names and Role Provisioning Services, aracın ders bağlamındaki kullanıcı listesini ve rollerini platformdan çekmesini sağlar. Launch tek bir kullanıcı hakkında bilgi taşır, NRPS ise sınıfın tamamını verir. Toplu ödev toplayan veya grup kuran araçlar bunu ister.
LTI aracını iframe içinde açmak güvenli mi?
Teknik olarak mümkün ama her araçta çalışmaz. Aracın X-Frame-Options ve CSP ayarları çerçevelemeyi reddedebilir, üçüncü taraf çerez kısıtları oturumu düşürebilir. Araç kaydında iframe ile yeni pencere arasında seçim bırakmak ve çerezleri SameSite=None ile işaretlemek pratik çözümdür.