LTI troubleshooting
Common failures when the HBG embedded UI does not appear inside a Canvas assignment iframe, and how to fix them.
For setup steps, see Canvas admin setup and Partner provisioning. For the self-hosted Canvas demo stack, see canvas-demo-ops in the grading-students repo.
Expected behavior
When LTI works, opening an External Tool assignment loads HBG inside the Canvas iframe:
- Canvas POSTs to
https://api.humansbutguided.com/api/lti/oidc-login - HBG redirects to Canvas
authorize_redirect - Canvas POSTs an
id_tokentohttps://api.humansbutguided.com/api/lti/launch - HBG redirects to
https://humansbutguided.com/embedded/courses/{id}/assignments/{id}?lti_launch_id=... - The embedded page calls
GET /api/lti/launches/{id}/embed-contextand renders Assignment Description, Grading Rubric, and Submission Portal
If any step fails, the iframe stays blank, shows a JSON error, or displays a Canvas error page.
Quick diagnosis
| What you see | Likely layer |
|---|---|
| No Content on the assignment | Canvas never auto-launches the tool (missing placement or broken assignment link) |
| Couldn't find valid settings for this link | Tool not installed in the course/account (ContextExternalTool missing or empty content_id) |
Canvas internal_server_error JSON in iframe | Canvas-side OIDC failure (often missing platform signing keys on self-hosted Canvas) |
Signature verification failed from HBG API | Canvas platform JWKS mismatch or wrong deployment_id in HBG |
Missing state param from HBG API | OIDC initiation URL misconfigured; login request hit /launch instead of /oidc-login |
| HBG embedded page loads but API 404 on embed-context | Launch never completed or lti_launch_id query param missing |
Check HBG backend logs for lines containing LTI oidc-login, LTI launch request, LTI launch complete, and embed-context.
Canvas configuration issues
"Couldn't find valid settings for this link"
Cause: The assignment references an external tool URL, but Canvas has no installed tool record (ContextExternalTool) for that link, so content_id is empty.
Fix:
- Install the app by Client ID at account and course level.
- Re-edit the assignment: Submission Type → External Tool → Find → Grading Students. Do not paste the launch URL manually without selecting the installed tool.
Assignment shows "No Content" (no iframe launch)
Cause: The developer key lacks the homework_submission placement, so Canvas does not embed the tool on the assignment submission page.
Fix: Add homework_submission (in addition to assignment_selection) in the LTI key placements, with the same target link URI and message type LtiResourceLinkRequest. Re-install the app by client ID if placements changed after the initial install.
Canvas posts login fields to /api/lti/launch (Missing state param)
Cause: Canvas built the launch form with action URL .../api/lti/launch instead of .../api/lti/oidc-login. This happens when DeveloperKey.oidc_initiation_url is empty even if the tool configuration JSON includes oidc_initiation_url.
Fix: Set the OIDC initiation URL on the Developer Key itself (not only inside tool configuration settings):
# Rails console on Canvas
key = DeveloperKey.find_by(name: "Grading Students")
key.update!(oidc_initiation_url: "https://api.humansbutguided.com/api/lti/oidc-login")For API provisioning, run the same update after creating the key (see Partner provisioning).
Canvas internal_server_error / Unknown Key Type during authorize
Cause (self-hosted Canvas only): Canvas platform LTI signing keys are missing. Lti::KeyStorage.present_key returns an empty JWK when dynamic_settings.yml has lti-keys: {}.
Fix: Generate and mount Canvas LTI signing keys. See LTI signing keys in the grading-students repo.
Managed Canvas Cloud instances handle this automatically; this applies to Docker/self-hosted demo stacks.
Invalid lti_message_hint / invalid JWT in Canvas logs
Cause: OIDC redirect reached Canvas with a fake or truncated lti_message_hint (common in manual curl tests). Real browser launches from Canvas include a signed JWT hint.
Fix: Test through a logged-in Canvas assignment launch, not by curling authorize_redirect with placeholder hints.
HBG backend configuration issues
Signature verification failed
Cause: HBG could not verify Canvas's id_token signature against the platform JWKS.
Common reasons:
- Self-hosted Canvas keys were rotated with duplicate
kidvalues across past/present/future keys — JWKS lookup picks the wrong public key. - HBG still caches old platform config after JWKS rotation (restart backend).
lti_platforms.jwks_urlorissueris wrong for the Canvas instance.
Fix:
- Ensure each Canvas LTI key in JWKS has a unique
kid(see LTI signing keys). - Confirm JWKS resolves:
curl https://<canvas>/api/lti/security/jwks - Restart the HBG backend to clear in-memory tool config cache.
- Verify the
lti_platformsrow:
| Column | Example (canvas-demo) |
|---|---|
issuer | https://canvas-demo.humansbutguided.com |
client_id | 10000000000002 |
jwks_url | https://canvas-demo.humansbutguided.com/api/lti/security/jwks |
auth_login_url | https://canvas-demo.humansbutguided.com/api/lti/authorize_redirect |
Launch fails on deployment_id
Cause: The deployment_id seeded in HBG does not match the value Canvas sends in the token. Placeholder values like 1:grading are incorrect for real installs.
Fix: Read the deployment ID from the installed tool on Canvas:
tool = ContextExternalTool.find_by(name: "Grading Students")
puts tool.deployment_id
# => e.g. "2:197df2e04ddcfcc6ddf0dd3a90cc00d28b9198c0"Update the HBG lti_platforms.deployment_id row (or re-run backend/scripts/seed_lti_platform.py with the correct value).
OIDC cookies dropped in iframe (Missing state param after oidc-login succeeds)
Cause: HBG set OIDC state cookies without Secure + SameSite=None because the backend did not detect HTTPS behind the reverse proxy.
Fix: Ensure the reverse proxy sends X-Forwarded-Proto: https. HBG derives secure cookies from that header when terminating TLS.
Intermittent Missing state param with multiple workers
Cause: HBG uses in-memory OIDC state storage; with UVICORN_WORKERS > 1, oidc-login and launch may hit different workers.
Fix: Run UVICORN_WORKERS=1 until launch state is persisted in Redis or the database.
Verification commands
# HBG tool JWKS (registered on the Canvas developer key)
curl -s https://api.humansbutguided.com/api/lti/jwks | jq '.keys[0] | {kty, alg, kid}'
# Canvas platform JWKS (what HBG uses to verify id_token FROM Canvas)
curl -s "https://<canvas-host>/api/lti/security/jwks" | jq '[.keys[] | .kid]'
# OIDC initiation (should 302 to Canvas authorize_redirect)
curl -sS -D - -o /dev/null -X POST "https://api.humansbutguided.com/api/lti/oidc-login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "iss=https://<canvas-host>&login_hint=1&target_link_uri=https://api.humansbutguided.com/api/lti/launch<i_message_hint=test&client_id=<client_id>"Successful end-to-end launch produces backend log lines:
LTI launch received | iss=https://<canvas-host>
LTI launch complete | launch_id=<uuid>
GET /api/lti/launches/<uuid>/embed-context 200Related docs
- Canvas admin setup
- Partner provisioning
- canvas-demo-ops (grading-students repo)
- Token requirements