Skip to content

robertjndw/go-lti-tool

Repository files navigation

go-lti-tool

A Go SDK for LTI 1.3 targeting tool implementations. Covers the full LTI Advantage surface: OIDC launch flow, Assignment & Grade Services (AGS), Names & Role Provisioning Services (NRPS), and Deep Linking.

Features

  • Framework-agnostic: exposes standard http.Handler — works with stdlib, chi, gin, echo, and any other router
  • Core / Advantage split: import only the packages you need
  • Typed LTI claims structs (no map[string]interface{})
  • Pluggable storage interfaces (bring your own DB, Redis, etc.)
  • JWKS endpoint handler with multi-key support
  • Full pagination support for AGS and NRPS

Packages

Package Purpose
github.com/robertjndw/go-lti-tool Core types, interfaces, defaults
.../login OIDC login initiation handler (step 1)
.../launch JWT validation + launch middleware (step 2)
.../jwks Serve the tool's public JWKS endpoint
.../dynreg LTI Dynamic Registration handler
.../advantage/ags Assignment & Grade Services
.../advantage/nrps Names & Role Provisioning Services
.../advantage/deeplink Deep Linking response builder

Quickstart

import (
    "context"
    "crypto/rand"
    "crypto/rsa"
    "fmt"
    "net/http"

    lti "github.com/robertjndw/go-lti-tool"
    "github.com/robertjndw/go-lti-tool/jwks"
)

func main() {
    // Load your RSA private key (see Key Management section below).
    privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) // dev only

    reg := &lti.Registration{
        Issuer:         "https://canvas.instructure.com",
        ClientID:       "your-client-id",
        KeySetURL:      "https://canvas.instructure.com/api/lti/security/jwks",
        AuthLoginURL:   "https://canvas.instructure.com/api/lti/authorize_redirect",
        AuthTokenURL:   "https://canvas.instructure.com/login/oauth2/token",
        ToolPrivateKey: privateKey,
        KID:            "key-1",
    }

    store := lti.NewMemoryStore() // swap for a DB-backed store in production
    _ = store.AddRegistration(context.Background(), *reg)
    _ = store.AddDeployment(context.Background(), reg.Issuer, lti.Deployment{DeploymentID: "your-deployment-id"})

    tool := lti.NewTool(
        lti.WithDataStore(store),
        lti.WithKeySet(jwks.FromRegistration(reg)),
    )

    mux := http.NewServeMux()
    mux.Handle("/oidc/login", tool.HandleLogin())
    mux.Handle("/lti/launch", tool.HandleLaunch(http.HandlerFunc(handleLaunch)))
    mux.Handle("/.well-known/jwks.json", tool.HandleJWKS())
    http.ListenAndServe(":8080", mux)
}

func handleLaunch(w http.ResponseWriter, r *http.Request) {
    ld, _ := lti.LaunchFromContext(r.Context())
    fmt.Fprintf(w, "Hello, %s!", ld.Claims.Name)
}

LTI Advantage Services

Assignment & Grade Services (AGS)

import "github.com/robertjndw/go-lti-tool/advantage/ags"

svc, err := ags.NewFromLaunch(ld)

// Find or create a line item.
li, err := svc.FindOrCreateLineitem(ctx, ags.Lineitem{
    Label: "Quiz 1", ScoreMaximum: 100,
})

// Submit a score.
err = svc.SubmitScore(ctx, li.ID, ags.Score{
    UserID:           ld.Claims.Subject,
    ScoreGiven:       85,
    ScoreMaximum:     100,
    ActivityProgress: ags.ActivityProgressCompleted,
    GradingProgress:  ags.GradingProgressFullyGraded,
    Timestamp:        time.Now().UTC().Format(time.RFC3339),
})

Names & Role Provisioning Services (NRPS)

import "github.com/robertjndw/go-lti-tool/advantage/nrps"

svc, err := nrps.NewFromLaunch(ld)
members, err := svc.GetMembers(ctx) // pagination handled automatically

Deep Linking

import "github.com/robertjndw/go-lti-tool/advantage/deeplink"

builder, err := deeplink.NewFromLaunch(ld)
html, err := builder.ResponseFormHTML([]deeplink.Resource{
    deeplink.NewLTIResourceLink("My Quiz", "https://tool.example.com/quiz/1"),
})
fmt.Fprint(w, html) // auto-submits to the platform

Examples

See the examples/ directory:

Example Description
basic-launch Minimal OIDC launch (core only)
grades Submit grades via AGS
deep-linking Deep linking content picker
complete Full LTI Advantage with all services
moodle Full LTI Advantage against a local Moodle instance via Docker (dynamic registration, deep linking, NRPS, AGS)

Key Management

Load your RSA private key from PEM:

pemBytes, _ := os.ReadFile("private.pem")
key, err := lti.ParsePrivateKey(pemBytes)

Generate a new key (development only):

key, _ := rsa.GenerateKey(rand.Reader, 2048)

Production Checklist

  • Replace MemoryNonceStore with a Redis/DB-backed implementation
  • Replace MemoryLaunchDataStore with persistent storage
  • Load private keys from a secret manager, not code
  • Serve your JWKS endpoint over HTTPS
  • Set appropriate cookie SameSite and Secure attributes for your deployment

Commands

go build ./...          # build all packages
go test ./...           # run all tests
go vet ./...            # static analysis

Reference

About

Golang SDK for LTI 1.3 Tool

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages