Operar LTI 1.3: rotación de claves, trazas y fallos explicables
Una guía operativa para localizar el fallo, rotar claves sin interrumpir lanzamientos y recuperar la integración sin relajar la frontera de confianza.

A las 9:02 un profesor informa de que la actividad externa muestra una pantalla en blanco. A las 9:05 soporte confirma que «LTI falla». El panel solo enseña respuestas 500 y el equipo no sabe si el problema está en Moodle, en la herramienta, en una clave, en el reloj del servidor, en una cookie bloqueada o en la devolución de una nota. Reiniciar parece más rápido que diagnosticar.
Este desconcierto procede del diseño operativo, no del estándar.
Para considerar lista una integración LTI 1.3, no basta con que un lanzamiento de prueba llegue a la herramienta. El equipo también debe poder localizar cada fallo en una fase y explicar por qué se rechazó una petición. Además, debe rotar claves sin un corte coordinado, limitar los reintentos y recuperar el servicio sin aceptar mensajes dudosos.
En LTI 1.3 en producción desarrollo la frontera de confianza: registro, lanzamiento, identidad y servicios. Esta guía empieza en el paso siguiente y trata LTI como una máquina de estados observable. Reducirlo a un controlador que «valida el JWT» y devuelve el mismo error para todo impediría operarlo con criterio.
La observabilidad exige conservar la decisión que tomó cada fase sin copiar los datos personales del token.
El incidente debe caer en una fase
LTI combina mensajes que pasan por el navegador y servicios entre servidores. Los mensajes originados por la plataforma —como un lanzamiento o LtiDeepLinkingRequest— llegan a la herramienta como un id_token firmado. Los originados por la herramienta —como LtiDeepLinkingResponse— regresan también como JWT firmado, pero en el parámetro de formulario JWT. Para AGS y NRPS, en cambio, la herramienta obtiene un access_token mediante una aserción propia y llama al servicio directamente. Confundir estos recorridos produce diagnósticos imposibles, como buscar una cookie para explicar un 403 del servicio de calificaciones.
Dividiría la operación en ocho fases:
| Fase | Entrada esperada | Evidencia de éxito | Fallos que deben distinguirse |
|---|---|---|---|
| Registro | Emisor, cliente, despliegue y direcciones acordadas | Versión activa identificable | Emisor desconocido, cliente ambiguo, despliegue retirado |
| Inicio OIDC | Petición no firmada de la plataforma | Transacción de un solo uso creada | Registro no resoluble, destino no permitido, inicio repetido |
| Autorización | Redirección al punto registrado | state y nonce vinculados al navegador | Estado perdido, retorno distinto, transacción caducada |
| Lanzamiento | id_token mediante form_post | Firma, campos del token y mensaje válidos | Firma, audiencia, tiempo, nonce, despliegue o tipo incorrectos |
| Política y sesión | Lanzamiento ya verificado | Recurso permitido y sesión local creada | Rol insuficiente, recurso retirado, cookie o marco bloqueado |
| Token de servicio | Aserción de la herramienta | Token con los alcances concedidos | Clave propia, audiencia, jti, cliente o alcance rechazados |
| Servicio LTI | Petición entre servidores a AGS o NRPS | Resultado de dominio confirmado | 401, 403, 404, 409, 429, 5xx, tiempo de espera o duplicado |
| Deep Linking | LtiDeepLinkingRequest, selección y LtiDeepLinkingResponse mediante el navegador | Retorno firmado validado y recursos incorporados | Firma, data, deployment_id, retorno o contenido rechazados |
La fase determina quién puede resolver el incidente y qué acción es segura. Si el state no existe, no se vuelve a consultar JWKS. Cuando la firma es válida pero el despliegue está retirado, refrescar claves no sirve. Del mismo modo, si AGS responde 403, relajar la validación del lanzamiento sería peligroso y además irrelevante.
La interfaz para la persona usuaria puede agrupar errores para no revelar detalles. La traza interna, en cambio, necesita un código estable, por ejemplo LTI-LAUNCH-AUD-MISMATCH. También debe explicar la fase, la decisión y la siguiente comprobación.
El registro también tiene historial
Una tabla con la configuración actual explica cómo debería funcionar LTI ahora, pero no cómo estaba configurado a las 9:02.
Modelaría el registro como un contrato versionado. Una versión incluye el issuer, el client_id, el punto de autorización, el punto de token, el jwks_uri, los algoritmos admitidos, las direcciones de inicio y retorno y sus fechas de vigencia. Cada deployment_id mantiene además propietario, estado, capacidades, política de datos y fecha de retirada.
Aunque no hace falta conservar secretos antiguos, sí es necesario registrar qué versión se usó para resolver una petición. También debe quedar claro por qué dejó de estar vigente. Cuando alguien cambia una dirección en producción, la modificación crea una versión y un evento de auditoría; no sobrescribe la explicación del día anterior.
Tres reglas eliminan mucha ambigüedad:
- El registro se resuelve por el emisor y el cliente acordados, no por el dominio visible en el navegador.
- El despliegue se autoriza después de validar el mensaje; conocer un
issuerno activa cualquier instalación de la herramienta. - Las direcciones de confianza proceden del registro. Un
jku,x5uo destino recibido dentro de un JWT no modifica de dónde se obtienen claves ni adónde se redirige.
La última regla aplica una recomendación del BCP de JWT: los campos y cabeceras recibidos no son configuración de confianza. El kid sirve para localizar una clave dentro del conjunto asociado previamente al emisor; no convierte el token en un directorio de claves.
Una traza útil no contiene el lanzamiento
El recorrido necesita un identificador de correlación creado al inicio y propagado hasta la sesión local. Para servicios asíncronos puede nacer otro identificador enlazado al primero. Ninguno debería depender del correo, el sub, el curso o el recurso.
Un evento técnico podría tener esta forma:
{
"trace_id": "01K0…",
"phase": "launch.validation",
"outcome": "rejected",
"reason": "LTI-LAUNCH-NONCE-REPLAY",
"registration_version": "reg-42:v7",
"deployment_ref": "dep:8f51…",
"message_type": "LtiResourceLinkRequest",
"kid": "platform-2026-07",
"algorithm": "RS256",
"received_at": "2026-07-28T09:02:14Z",
"duration_ms": 18
}
deployment_ref puede ser una referencia interna o un valor seudonimizado si el identificador real revela a la institución. El evento no guarda el token, el nombre, el correo, los roles completos, los parámetros de inicio ni el access_token. Si una investigación necesita un dato adicional, debe existir una vía controlada y temporal para obtenerlo; no una copia permanente en cada índice de registros.
Registraría por fase:
- versión de configuración elegida y resultado de la resolución;
- creación, consumo, caducidad o repetición de
stateynonce, sin guardar sus valores en claro; kid, algoritmo y origen de la clave usada;- validaciones de emisor, audiencia, tiempo y campos LTI como resultados discretos;
- decisión de política sobre despliegue, tipo de mensaje y recurso;
- resultado HTTP y semántico de los servicios, alcance solicitado y concedido;
- reintento, demora, circuito abierto y resultado final.
Así se puede buscar «todas las firmas rechazadas tras la versión v7» sin exponer lanzamientos. También se evita la falsa solución de activar un nivel de registro detallado durante la incidencia y descubrir después que ha volcado credenciales y datos académicos.
Un kid desconocido no autoriza una búsqueda sin límites
La guía de implementación de 1EdTech establece que la plataforma publica sus claves mediante un JWK Set, que cada kid identifica una clave inmutable y que la herramienta debe volver a consultar el jwks_uri cuando no encuentra ese identificador en caché. Esa recuperación permite rotar las claves, siempre que se aplique con límites.
El algoritmo operativo debería ser:
- buscar el
kiden la caché asociada alissuery aljwks_uriregistrados; - si no existe, comprobar la caché negativa, el intervalo mínimo desde la última recarga y el presupuesto de actualizaciones del emisor;
- cuando se permita actualizar, coordinar una única descarga para ese emisor, de modo que cien lanzamientos simultáneos no produzcan cien peticiones;
- consultar solo la dirección registrada, con tiempo de espera, tamaño y formato acotados;
- sustituir la vista de claves únicamente si el conjunto es válido;
- volver a buscar el
kiduna vez; - si sigue sin aparecer, rechazar el lanzamiento con un motivo específico y conservar brevemente ese identificador en la caché negativa.
La coordinación evita la estampida concurrente, pero no basta frente a identificadores aleatorios enviados de forma secuencial. El intervalo mínimo, el presupuesto por emisor y una caché negativa de corta duración limitan esa amplificación sin impedir una rotación con solape. Tampoco seguiría una dirección incluida en el token: además de romper el registro de confianza, abre la puerta a peticiones del servidor hacia destinos no autorizados.
Hay dos estados que no deben confundirse. No pude actualizar JWKS significa que la fuente registrada no respondió o devolvió un conjunto inválido. Actualicé JWKS y el kid no existe significa que el mensaje cita una clave que la plataforma no publica. Ambos terminan en rechazo, pero el primero puede ser una indisponibilidad temporal y el segundo apunta a una configuración o firma incorrectas.
Disponibilidad no es aceptar con la última clave Si el token cita un
kiddesconocido, probar todas las claves antiguas o ignorar la cabecera puede convertir una incidencia en una ruptura de la frontera de confianza. La recuperación conserva las mismas validaciones; solo actualiza el conjunto autorizado.
La rotación necesita solape y una reversión preparada
Una rotación segura crea un periodo durante el que emisores y verificadores aceptan el cambio. Durante ese intervalo todavía debe poder explicarse qué clave se usó; sustituirla a una hora acordada no basta.
Para rotar una clave de la herramienta —la que firma aserciones para obtener tokens o respuestas de Deep Linking— seguiría este orden:
- generar la clave nueva en el gestor autorizado y asignarle un
kidnuevo; - publicar la clave pública nueva junto a la anterior;
- comprobar desde fuera que el JWK Set correcto se propaga a todos los entornos y cachés relevantes;
- empezar a firmar con la clave nueva en un ámbito controlado;
- observar solicitudes de token, rechazos por
kidy firmas todavía emitidas con la anterior; - extender el cambio al resto de despliegues;
- retirar la clave pública anterior cuando haya terminado la ventana real de validez y no se observen usos legítimos;
- revocar el material privado anterior según la política de custodia.
La ventana no debería copiarse de una receta. Se calcula con la vida máxima de los mensajes emitidos, el margen de reloj admitido, la caché efectiva y el tiempo de propagación observado. La propia guía de seguridad de 1EdTech recomienda conservar la clave pública anterior durante el solape. Conservarla para siempre tampoco es rotación: amplía el inventario de claves aceptadas y dificulta saber cuál debería estar en uso.
La reversión debe estar decidida antes del paso cuatro. Mientras ambas claves públicas siguen publicadas, volver a firmar con la anterior puede restaurar servicio sin redistribuir configuración. Si la clave antigua se ha comprometido, esa reversión está prohibida; el incidente exige revocación y coordinación, no disponibilidad a cualquier precio.
La plataforma rota su propia clave con el mismo principio de solape, pero la herramienta no controla el calendario. Por eso debe refrescar ante un kid desconocido y medir cuánto tarda en reconocer la clave nueva.

Los servicios se recuperan fuera del camino del navegador
El id_token del lanzamiento no es el token para llamar a AGS o NRPS. La herramienta firma una aserción, solicita un access_token al punto registrado y pide únicamente los alcances que necesita. El fallo puede estar en esa autenticación o en la operación posterior; conviene medirlas por separado.
No bloquearía el lanzamiento porque NRPS tarde en responder o porque una escritura de calificación necesite reintentarse. Una vez que el navegador confirma la entrada de la persona, las operaciones entre servidores pueden pasar a una cola duradera. Para ello, el producto debe admitir la ejecución diferida. Cada trabajo conserva el ámbito, el alcance, la operación y la clave de idempotencia del dominio, además del plazo después del cual ya no tiene sentido ejecutarlo.
La política de reintentos depende del resultado:
| Resultado | Decisión inicial | Condición para reintentar |
|---|---|---|
| Tiempo de espera o red interrumpida | Resultado desconocido | Consultar estado o usar idempotencia antes de repetir una mutación |
| 401 | Credencial no aceptada | Renovar una vez si el token caducó; no repetir si falla la aserción |
| 403 | Operación no autorizada | No reintentar sin un cambio de alcance, despliegue o política |
| 404 | Recurso o dirección no válido | Verificar identificador y versión de configuración |
| 409 | Conflicto de estado | Reconciliar el estado actual antes de decidir |
| 429 | Límite del proveedor | Respetar Retry-After y presupuesto del trabajo |
| 5xx | Fallo temporal posible | Reintento acotado con espera creciente y variación aleatoria |
«POST falló, repetir» no es una estrategia. Si la plataforma procesó la nota y la respuesta se perdió, una segunda petición puede duplicar o sobrescribir una operación según el servicio. El adaptador necesita conocer la semántica, comparar el estado remoto cuando sea posible y registrar qué resultado considera definitivo.
Las métricas deben conducir a una decisión
Un panel con volumen total de lanzamientos no avisa de que una sola institución lleva veinte minutos sin poder entrar. Mediría tasas y latencias por fase, producto, versión de registro y despliegue seudonimizado, con cuidado de no convertir identificadores de alta cardinalidad en etiquetas de la plataforma de métricas.
Las señales mínimas serían:
- lanzamientos iniciados, aceptados y rechazados por código de motivo;
- transacciones OIDC creadas, caducadas y repetidas;
- aciertos de caché JWKS, actualizaciones, errores y
kidausentes tras actualizar; - uso de cada
kidpara firmar y validar durante una rotación; - creación de sesión y errores específicos de navegador o marco;
- solicitudes de token por alcance y resultado;
- latencia, códigos, reintentos y agotamiento de trabajos de AGS y NRPS;
- antigüedad de la operación pendiente más vieja;
- versiones de configuración activadas, revertidas y retiradas.
Alertaría sobre síntomas que tienen una respuesta. Un aumento de state perdido afecta al flujo del navegador; un kid desconocido concentrado en un emisor apunta a rotación o configuración; un 403 en AGS para un único despliegue obliga a revisar alcances; una cola de notas envejecida exige frenar o ampliar la recuperación. «Más errores LTI» no indica quién debe hacer qué.
Los objetivos de servicio también se separan. La tasa de lanzamientos aceptados mide el acceso interactivo, mientras que el tiempo transcurrido hasta confirmar una calificación mide una operación diferida. Si se mezclan, miles de lanzamientos pueden ocultar cien notas atascadas.
El banco de pruebas debe atacar las suposiciones
Antes de declarar operable la integración ejecutaría al menos estas pruebas negativas en un entorno representativo:
- emisor no registrado y emisor válido con cliente incorrecto;
stateausente, caducado, de otro navegador y consumido dos veces;nonceincorrecto y repetido;- token con
expcaducado;iatausente, no numérico, futuro o fuera de la ventana de frescura definida; y, cuando aparezca,nbffuera del margen admitido; - algoritmo no permitido, firma alterada y clave que pertenece a otro emisor;
audincorrecto y audiencia múltiple conazpno autorizado;kidnuevo disponible tras actualizar JWKS;kidinexistente incluso después de actualizar;- JWKS lento, no disponible, demasiado grande o con formato inválido;
deployment_iddesconocido, retirado o asociado a otro registro;- tipo o versión de mensaje incorrectos y campo obligatorio ausente;
- destino firmado no permitido y recurso retirado;
- cookies de sesión bloqueadas en un
iframey lanzamiento en ventana nueva; - aserción de servicio repetida, audiencia incorrecta o alcance denegado;
- AGS o NRPS con 401, 403, 404, 409, 429, 5xx, tiempo de espera y respuesta perdida;
- dos trabajadores recuperando el mismo trabajo y llegada fuera de orden.
Cada prueba define respuesta visible, código interno, cambio de estado, métrica esperada y ausencia de datos sensibles en los registros. No basta con comprobar que devuelve 400. La prueba debe demostrar que el equipo sabría qué hacer con ese 400.
La certificación de 1EdTech ayuda a comprobar conformidad. No sustituye este banco: una implementación puede cumplir el intercambio y seguir sin tener rotación ensayada, cola duradera, trazas utilizables o una guía operativa propia.
La guía operativa empieza por contener el fallo
Cuando llega una incidencia, seguiría esta secuencia:
- Localizar la fase. Obtener un
trace_idy el primer resultado que se apartó del recorrido esperado. - Acotar el alcance. Determinar si afecta a un emisor, cliente, despliegue, versión, navegador,
kid, servicio o a todos. - Detener daño adicional. Pausar reintentos que duplican operaciones, limitar una cola creciente o retirar una versión defectuosa. No desactivar firma, audiencia,
stateononce. - Comparar cambios. Revisar versiones de registro, rotaciones, reloj, despliegues y publicación de JWKS en la ventana del incidente.
- Reproducir el rechazo. Usar un vector sin datos personales que produzca el mismo código de motivo.
- Recuperar con una acción reversible. Revertir configuración o firma cuando la clave anterior siga siendo segura; reanudar trabajos con idempotencia; desactivar solo el despliegue afectado si el modelo lo permite.
- Verificar el resultado. Confirmar lanzamiento, sesión y servicios por separado y observar la cola hasta vaciarla.
- Cerrar la explicación. Guardar cronología, causa, detección, alcance, acción, evidencia de recuperación y prueba que evitará la regresión.
La recuperación tiene condiciones de parada. Si no se puede vincular la clave al emisor registrado, el lanzamiento se rechaza. Cuando una aserción puede haberse repetido, no se concede el token solo para recuperar disponibilidad. Si una escritura tiene un resultado desconocido y carece de reconciliación, se detiene antes de duplicarla. El modo degradado puede ocultar una función auxiliar, pero nunca inventa confianza.
Doce pruebas antes de llamar a LTI un servicio
Consideraría lista la operación cuando el equipo puede demostrar:
- configuración versionada y reversible por registro y despliegue;
- códigos de rechazo estables para cada fase;
state,noncey aserciones de un solo uso probados contra repetición;- claves ligadas a emisores y algoritmos permitidos explícitamente;
- actualización JWKS ante
kiddesconocido con límites y control de concurrencia; - rotación propia ensayada con solape, observación y reversión;
- trazas correlacionadas sin tokens ni datos personales;
- sesiones de navegador diagnosticables sin confundirlas con firma o política;
- servicios desacoplados del lanzamiento cuando el proceso lo admite;
- reintentos acotados, reconciliación e idempotencia para mutaciones;
- paneles y alertas vinculados a acciones concretas;
- un simulacro en el que otra persona siga la guía operativa y recupere el servicio.
El último punto revela documentación que solo entiende quien escribió la integración. Una guía operativa no se valida leyéndola, sino observando si una persona de guardia puede usarla bajo presión sin llamar al autor para interpretar cada paso.
Operar es conservar la frontera durante el fallo
LTI 1.3 sustituye secretos compartidos por un modelo más explícito de confianza. Esa mejora desaparece si, ante el primer incidente, el equipo prueba todas las claves, acepta cualquier audiencia, reintenta sin límite o vuelca tokens para entender qué ocurre.
Una operación madura divide el recorrido, conserva qué configuración decidió cada petición, rechaza con motivos precisos, rota mediante solape, aísla los servicios del navegador y convierte los fallos esperados en pruebas repetibles.
El objetivo consiste en que cada fallo tenga un ámbito, una explicación y una recuperación que no obligue a debilitar aquello que la integración pretendía proteger.
Más de 22 años construyendo y evolucionando plataformas de aprendizaje que tienen que operar de verdad.
Seguir leyendo
- OpenTelemetry en Moodle 5.2: cómo gobernar la telemetría en producción
Arquitectura, muestreo, privacidad y criterios de aceptación para operar OpenTelemetry en Moodle 5.2 sin convertir la telemetría en otra fuente de coste y ruido.
- LTI 1.3, API o xAPI: cómo elegir la frontera correcta
LTI 1.3, una API de dominio y xAPI resuelven fronteras distintas. Esta comparativa propone seis preguntas para elegir sin enfrentar estándares que no compiten entre sí.
- LTI 1.3 en producción: una frontera de confianza
LTI 1.3 conecta un LMS con herramientas externas mediante mensajes firmados y servicios acotados. Esta guía explica los contratos que hay que diseñar para llevarlo a producción sin confundir inicio de sesión, identidad y autorización.