Skip to content

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:

  1. Canvas POSTs to https://api.humansbutguided.com/api/lti/oidc-login
  2. HBG redirects to Canvas authorize_redirect
  3. Canvas POSTs an id_token to https://api.humansbutguided.com/api/lti/launch
  4. HBG redirects to https://humansbutguided.com/embedded/courses/{id}/assignments/{id}?lti_launch_id=...
  5. The embedded page calls GET /api/lti/launches/{id}/embed-context and 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 seeLikely layer
No Content on the assignmentCanvas never auto-launches the tool (missing placement or broken assignment link)
Couldn't find valid settings for this linkTool not installed in the course/account (ContextExternalTool missing or empty content_id)
Canvas internal_server_error JSON in iframeCanvas-side OIDC failure (often missing platform signing keys on self-hosted Canvas)
Signature verification failed from HBG APICanvas platform JWKS mismatch or wrong deployment_id in HBG
Missing state param from HBG APIOIDC initiation URL misconfigured; login request hit /launch instead of /oidc-login
HBG embedded page loads but API 404 on embed-contextLaunch 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

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:

  1. Install the app by Client ID at account and course level.
  2. 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):

ruby
# 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:

  1. Self-hosted Canvas keys were rotated with duplicate kid values across past/present/future keys — JWKS lookup picks the wrong public key.
  2. HBG still caches old platform config after JWKS rotation (restart backend).
  3. lti_platforms.jwks_url or issuer is wrong for the Canvas instance.

Fix:

  1. Ensure each Canvas LTI key in JWKS has a unique kid (see LTI signing keys).
  2. Confirm JWKS resolves: curl https://<canvas>/api/lti/security/jwks
  3. Restart the HBG backend to clear in-memory tool config cache.
  4. Verify the lti_platforms row:
ColumnExample (canvas-demo)
issuerhttps://canvas-demo.humansbutguided.com
client_id10000000000002
jwks_urlhttps://canvas-demo.humansbutguided.com/api/lti/security/jwks
auth_login_urlhttps://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:

ruby
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

bash
# 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&lti_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 200