Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LTI Hello World with Django

A minimal working example of an LTI 1.3 tool built with Django, using pylti1p3next. It connects to Moodle and renders a simple "Hello World" page inside an iframe — the shortest path from zero to a working LTI integration.


Table of Contents


Prerequisites

  • Python 3.10+
  • pip
  • openssl
  • A running and accessible Moodle instance

Project Setup

mkdir lti-hello && cd lti-hello
python -m venv venv
source venv/bin/activate   # Linux/macOS
# venv\Scripts\activate    # Windows

pip install django
pip install git+https://github.com/pymsglobal/pylti1p3next.git

django-admin startproject config .
python manage.py startapp lti
mkdir -p lti/templates/lti

Expected project structure:

lti-hello/
├── config/
│   ├── settings.py
│   └── urls.py
├── lti/
│   ├── views.py
│   ├── urls.py
│   └── templates/lti/hello.html
├── private.key
├── public.key
└── manage.py

RSA Key Generation

LTI 1.3 uses RSA key pairs to sign and verify JWT tokens exchanged with Moodle:

openssl genrsa -out private.key 4096
openssl rsa -in private.key -pubout -out public.key

Security note: Never commit private.key to version control. Add it to .gitignore.


Django Configuration

config/settings.py

Add 'lti' to INSTALLED_APPS and set the following options:

INSTALLED_APPS = [
    ...
    'lti',
]

# Required for rendering inside a Moodle iframe
X_FRAME_OPTIONS = 'ALLOWALL'

PYLTI1P3_TOOL_CONF = {
    "private_key_file": BASE_DIR / "private.key",
    "public_key_file": BASE_DIR / "public.key",
}

config/urls.py

from django.urls import path, include

urlpatterns = [
    path('lti/', include('lti.urls')),
]

lti/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('login/', views.login, name='lti-login'),
    path('launch/', views.launch, name='lti-launch'),
    path('jwks/', views.jwks, name='lti-jwks'),
]

LTI Views

Create lti/views.py. Replace CLIENT_ID_MOODLE with the Client ID provided by Moodle after registering the tool (see Moodle Configuration).

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render
from pylti1p3.contrib.django import DjangoOIDCLogin, DjangoMessageLaunch
from pylti1p3.tool_config import ToolConfDict
from django.conf import settings

def get_tool_conf():
    return ToolConfDict({
        "http://localhost": [{
            "default": True,
            "client_id": "CLIENT_ID_MOODLE",
            "auth_login_url": "http://localhost/mod/lti/auth.php",
            "auth_token_url": "http://localhost/mod/lti/token.php",
            "key_set_url": "http://localhost/mod/lti/certs.php",
            "key_set": None,
            "private_key_file": str(settings.BASE_DIR / "private.key"),
            "public_key_file": str(settings.BASE_DIR / "public.key"),
            "deployment_ids": ["1"]
        }]
    })

@csrf_exempt
def login(request):
    tool_conf = get_tool_conf()
    oidc_login = DjangoOIDCLogin(request, tool_conf)
    return oidc_login.redirect("http://localhost:8000/lti/launch/")

@csrf_exempt
def launch(request):
    tool_conf = get_tool_conf()
    message_launch = DjangoMessageLaunch(request, tool_conf)
    message_launch.validate()
    return render(request, 'lti/hello.html')

def jwks(request):
    tool_conf = get_tool_conf()
    return JsonResponse(tool_conf.get_jwks())

Template

Create lti/templates/lti/hello.html:

<!DOCTYPE html>
<html>
<head><title>LTI Hello World</title></head>
<body>
    <h1>Hello from Django LTI!</h1>
    <p>The connection with Moodle is working correctly.</p>
</body>
</html>

Moodle Configuration

In Moodle, go to: Site administration → Plugins → Activity modules → Manage tools → Configure a tool manually

Field Value
Tool name Django Hello World
Tool URL http://localhost:8000/lti/launch/
LTI version LTI 1.3
Initiate login URI http://localhost:8000/lti/login/
Redirection URI(s) http://localhost:8000/lti/launch/
Public keyset URL http://localhost:8000/lti/jwks/

After saving, Moodle will display a Client ID. Copy it into get_tool_conf() in views.py to replace CLIENT_ID_MOODLE.


Running the Server

python manage.py migrate
python manage.py runserver

Then add the tool as an activity in a Moodle course.


How It Works

The LTI 1.3 launch follows the OpenID Connect third-party login flow:

Moodle                          Django
  │                               │
  │── OIDC login request ────────>│ /lti/login/
  │                               │  (DjangoOIDCLogin)
  │<── redirect to Moodle ────────│
  │                               │
  │── ID token (POST) ──────────>│ /lti/launch/
  │                               │  (DjangoMessageLaunch.validate())
  │<── Hello World page ──────────│
  1. Moodle sends an OIDC login request to /lti/login/
  2. Django redirects back to Moodle for authentication
  3. Moodle POSTs a signed JWT to /lti/launch/
  4. Django validates the token and renders the Hello World page inside the iframe

Appendix — Standard Moodle URLs

Replace <moodle> with your Moodle or your LMS instance hostname or IP.

Parameter URL
auth_login_url http://<moodle>/mod/lti/auth.php
auth_token_url http://<moodle>/mod/lti/token.php
key_set_url http://<moodle>/mod/lti/certs.php

About

Working example of an LTI 1.3 tool built with Django and pylti1p3next, featuring deep linking, NRPS and AGS

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages