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.
- 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
| 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 |
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 := <i.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)
}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),
})import "github.com/robertjndw/go-lti-tool/advantage/nrps"
svc, err := nrps.NewFromLaunch(ld)
members, err := svc.GetMembers(ctx) // pagination handled automaticallyimport "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 platformSee 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) |
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)- Replace
MemoryNonceStorewith a Redis/DB-backed implementation - Replace
MemoryLaunchDataStorewith persistent storage - Load private keys from a secret manager, not code
- Serve your JWKS endpoint over HTTPS
- Set appropriate cookie
SameSiteandSecureattributes for your deployment
go build ./... # build all packages
go test ./... # run all tests
go vet ./... # static analysis