Skip to content

alejlatorre/lti-provider

Repository files navigation

LTI Provider

FastAPI-based LTI 1.3 provider for Canvas LMS integration.

Getting started? Follow the step-by-step Setup Guide to go from zero to a working Canvas launch.

About

LTI Provider is a FastAPI implementation of the Learning Tools Interoperability (LTI) 1.3 standard, designed to facilitate seamless integration of external learning tools within Learning Management Systems (LMS) like Canvas. This application supports OIDC (OpenID Connect) for secure authentication and JWT (JSON Web Tokens) for message signing, ensuring a robust and secure connection between the LMS and external tools.

Key Features:

  • LTI 1.3 Compliance: Secure tool launches and user authentication following the LTI 1.3 standard.
  • Multi-tenant platform registry: Platforms are resolved per launch by (issuer, client_id) from a database registry (LTIPlatform + LTIDeployment), so a single deployment can serve many LMS instances. A default platform can be seeded from environment variables for single-tenant setups.
  • Secure OIDC flow: One-time-use state/nonce stored server-side with a TTL and consumed at launch, preventing CSRF and id_token replay.
  • Robust JWT validation: Launch id_token signatures are verified against the platform's JWKS (kid-aware, cached), together with iss/aud/exp/iat/nonce/azp, LTI version, message type and registered deployment_id.
  • Persistent signing key: A stable RSA keypair (with an RFC 7638 kid) is loaded or generated once, so the published JWKS keeps verifying the tokens the tool signs.
  • LTI Advantage: Names and Role Provisioning (NRPS), Assignment and Grade Services (AGS), and Deep Linking, backed by a client_credentials access-token service.
  • Local Development: Easy setup for local testing with ngrok support.

Technical Overview

LTI 1.3 Protocol Flow

  1. OIDC Login Initiation

    • Canvas initiates login by sending a POST request to /lti/login
    • Contains platform info, client_id, and login hints
    • Provider validates and redirects to Canvas auth endpoint
  2. Authentication & Launch

    • Canvas authenticates and sends signed JWT (id_token) to /lti/launch
    • Provider validates JWT signature using platform's public JWK
    • Claims are verified (issuer, audience, expiry, etc.)
    • User is redirected to the tool's interface
  3. Security

    • Uses RSA key pairs for message signing
    • JWT validation ensures message authenticity
    • OIDC (OpenID Connect) handles identity verification
    • Session management via secure cookies

Developer Key Configuration Explained

The Developer Key JSON configuration establishes the security and communication parameters between Canvas and your tool:

{
  "title": "Your Tool Name",
  "description": "Your tool description",
  "target_link_uri": "http://your-domain/lti/launch",
  "oidc_initiation_url": "http://your-domain/lti/login",
  "public_jwk_url": "http://your-domain/.well-known/jwks.json",
  "scopes": [
    "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
    "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
    "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"
  ]
}
  1. Authentication Endpoints

    • target_link_uri: Entry point for tool launches
    • oidc_initiation_url: Handles OIDC login flow initiation
    • public_jwk_url: Provides RSA public keys for JWT verification
  2. Security Flow

    • Canvas initiates OIDC login via oidc_initiation_url
    • Tool validates request and redirects to Canvas
    • Canvas sends authenticated launch to target_link_uri
    • Tool verifies JWT using keys from public_jwk_url
  3. Scopes

    • lineitem: Create/manage gradebook columns
    • result.readonly: Read student results
    • contextmembership.readonly: Access course roster
  4. JWT Claims The launch JWT includes:

    • iss: Canvas instance URL
    • aud: Your Client ID
    • sub: User identifier
    • https://purl.imsglobal.org/spec/lti/claim/...: LTI-specific data
  5. Security Considerations

    • All URLs must use HTTPS in production
    • JWKs must be regularly rotated
    • Nonce validation prevents replay attacks
    • State parameter ensures request continuity

API Endpoints

LTI Configuration

  • GET /lti/config
    • Returns tool configuration for Canvas integration
    • Defines scopes, URLs, and placement settings
    • Used during tool installation in Canvas

OIDC Flow

  • GET / POST /lti/login

    • Handles OIDC third-party login initiation (platforms may use either method)
    • Resolves the platform, mints a one-time state/nonce, and redirects to the platform's authorization endpoint
  • POST /lti/launch

    • Main entry point for resource-link launches and Deep Linking requests
    • Consumes the state, verifies the id_token signature and claims, persists the launch
    • Resource link → redirects to the tool UI with a session token; Deep Linking → returns a signed LtiDeepLinkingResponse

LTI Advantage Services

These are called from the tool UI using the launch session token (?lti=<token> or Authorization: Bearer):

  • GET /lti/names-and-roles
    • NRPS: returns the course roster for the launch's context
  • GET /lti/line-items
    • AGS: lists the gradebook line items available to the launch's context

Security Endpoints

  • GET /.well-known/jwks.json
    • Serves the tool's stable public JWK for token verification
    • Used by the platform to verify tool-signed messages (Deep Linking responses, service assertions)

Prerequisites

  • Python 3.13+
  • uv package manager
  • PostgreSQL

Or skip all of the above and use Docker.

Quickstart with Docker

The fastest way to run the provider with a database. docker compose starts PostgreSQL and the app, waits for the database to be healthy, and the app creates its tables automatically on startup — no manual database setup.

docker compose up --build

Then:

  • App: http://localhost:8000 — e.g. curl http://localhost:8000/.well-known/jwks.json
  • Config JSON for the Developer Key: curl http://localhost:8000/lti/config
  • Expose it for an LMS to reach: ngrok http 8000

Notes:

  • DATABASE_URL is wired to the bundled db service automatically.
  • The tool's signing key is generated once and persisted in the lti_keys volume.
  • To register the LMS you connect to, set the LTI_* variables (e.g. in a .env file next to docker-compose.yml) — they seed a platform on startup. See .env.example.
  • Reset everything (including the database) with docker compose down -v.

Local Development Setup (without Docker)

  1. Clone and install

    git clone https://github.com/alejlatorre/lti-provider.git
    cd lti-provider
    uv sync
  2. Environment Configuration

    cp .env.example .env
    # Edit .env with your settings:
    # - Database URL
    # - LTI credentials
    # - Tool URLs
  3. Database Setup

    # The application will automatically create tables on startup
  4. Run Development Server

    uv run uvicorn app.main:app --reload
  5. Expose locally (for Canvas testing)

    ngrok http 8000
  6. Canvas Configuration

    • Add Developer Key in Canvas
    • Configure with your tool's URLs (use GET /lti/config to generate the JSON)
    • Install in desired course

Simplified Flow

  1. Teacher adds tool to Canvas course
  2. Student clicks tool link
  3. Canvas sends login request to tool
  4. Tool validates and redirects to Canvas
  5. Canvas authenticates and launches tool
  6. Tool verifies launch and shows interface

Folder Structure

lti-provider/
├── app/
│   ├── main.py
│   ├── config.py
│   ├── constants.py            # LTI claim URIs, message types, roles, scopes
│   ├── models/
│   │   ├── database.py
│   │   └── lti.py              # LTIPlatform, LTIDeployment, LTILoginState, LTILaunch
│   ├── routes/
│   │   ├── home.py
│   │   └── lti.py
│   └── services/
│       ├── keys.py             # persistent RSA key + JWKS
│       ├── jwt_service.py      # verify platform tokens / sign tool tokens
│       ├── session.py          # tool session token
│       ├── lti.py              # OIDC login + launch validation + claim parsing
│       ├── access_token.py     # client_credentials grant (LTI Advantage)
│       ├── nrps.py             # Names and Role Provisioning client
│       ├── ags.py              # Assignment and Grade Services client
│       └── deep_linking.py     # Deep Linking response signing
├── docs/
│   ├── CONTRIBUTING.md
│   └── SECURITY.md
├── tests/
│   ├── conftest.py
│   ├── test_lti.py              # unit tests (keys, roles, claims, JWT, deep linking)
│   ├── test_launch_flow.py      # login → launch integration test
│   └── create_test_deployment.py
├── Dockerfile
├── docker-compose.yml           # app + PostgreSQL
├── SETUP.md                     # step-by-step Canvas setup guide
├── pyproject.toml
├── uv.lock
├── .python-version
├── .pre-commit-config.yaml
├── .env.example
├── LICENSE
├── CHANGELOG.md
├── CONTRIBUTING.md
└── SECURITY.md

Canvas Integration Setup

1. Create Developer Key

  1. Access Canvas Admin settings

  2. Navigate to "Developer Keys"

  3. Click "+ Developer Key" and select "LTI Key"

  4. Configure the key:

    You can get the configuration by making a GET request to:

    curl http://your-domain/lti/config

    Which returns:

    {
      "title": "Your Tool Name",
      "description": "Your tool description",
      "target_link_uri": "http://your-domain/lti/launch",
      "oidc_initiation_url": "http://your-domain/lti/login",
      "public_jwk_url": "http://your-domain/.well-known/jwks.json",
      "scopes": [
        "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
        "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
        "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"
      ],
      "extensions": [
        {
          "platform": "canvas.instructure.com",
          "settings": {
            "platform": "canvas.instructure.com",
            "text": "Your Tool Name",
            "placements": [
              {
                "placement": "course_navigation",
                "enabled": true,
                "default": "enabled",
                "message_type": "LtiResourceLinkRequest",
                "target_link_uri": "http://your-domain/lti/launch"
              }
            ]
          }
        }
      ]
    }

    Copy this configuration into the Developer Key settings in Canvas.

  5. Save and enable the Developer Key

  6. Note the Client ID (will be used in .env)

2. Configure Environment Variables

Update your .env file. For a single-tenant setup, the LTI_* variables seed a platform row on startup; multi-tenant setups insert LTIPlatform / LTIDeployment rows directly instead.

# Tool
TOOL_URL="https://your-domain"        # endpoint URLs are derived from this
DATABASE_URL="postgresql+asyncpg://user:pass@localhost:5432/lti_db"
SECRET_KEY="a-long-random-string"

# Default platform seed (the LMS this tool connects to)
LTI_ISSUER="https://canvas.instructure.com"          # platform issuer (iss)
LTI_CLIENT_ID="10000000000001"                       # from the Developer Key
LTI_DEPLOYMENT_ID="1:df26f9c414..."                  # created on course install
LTI_AUTH_LOGIN_URL="https://sso.canvaslms.com/api/lti/authorize_redirect"
LTI_AUTH_TOKEN_URL="https://sso.canvaslms.com/login/oauth2/token"   # AGS/NRPS
LTI_JWK_URL="https://sso.canvaslms.com/api/lti/security/jwks"       # platform JWKS

The tool's own signing key is auto-generated at keys/private.pem on first run (and reused afterwards). To pin it, set LTI_PRIVATE_KEY (PEM) or LTI_PRIVATE_KEY_PATH. See .env.example for all options.

3. Install in Course

  1. Go to Course Settings
  2. Select "Apps" tab
  3. Click "+ App"
  4. Choose "By Client ID"
  5. Enter the Client ID from Developer Key
  6. Submit and authorize the installation
  7. The tool will appear in course navigation

4. Testing the Integration

  1. Development Environment

    # Start your development server
    uv run uvicorn app.main:app --reload
    
    # For local testing, use ngrok to expose your server
    ngrok http 8000
  2. Update Canvas Configuration

    • If testing locally, update Developer Key URLs with ngrok domain
    • Update .env with corresponding URLs
  3. Verify Installation

    • Access course as instructor
    • Tool should appear in course navigation
    • Click to test launch flow
    • Check application logs for debugging

Contributing

Contributions are welcome! See docs/CONTRIBUTING.md to get started.

License

Apache-2.0 — see LICENSE for details.

About

A FastAPI-based Learning Tools Interoperability (LTI) 1.3 provider implementation

Topics

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors