The Instructure Community will enter a read-only state on November 22, 2025 as we prepare to migrate to our new Community platform in early December.
Read our blog post for more info about this change.
Found this content helpful? Log in or sign up to leave a like!
Hi,
I am building an LTI app and have some questions about ContentItemSelection in the context of a launch in Canvas.
Background:
I am developing against Canvas running all the Canvas services in Docker containers via docker-compose of off branch "stable". I have successfully been able to do a basic, authenticated, LTI Launch Request against the Tool Provider I'm developing.
I now want to extend this so as to be able to let educators choose their content and create an LTI launch link dynamically, via ContentItemSelection.
I am using the instructure/ims-lti Ruby library, running in a Rack application, in order to authenticate signed messages. This is working fine.
I have now configured my external tool through Canvas via an XML doc, from the canvas website, here, for a placement in Assignments - https://canvas.instructure.com/doc/api/file.content_item.html
Problem:
Now that I have my tool configured in canvas I see the request coming into my TP as a "ContentItemSelectionRequest" with a "content_item_return_url" parameter. This is good, and what I expect.
At this point, when the request is received server side, I am turning around and, for testing purposes, POSTing to the "content_item_return_url", server-side ( not in the browser via AJAX ) with a payload that I am building using the ims/lti repo, using an instance of IMS::LTI::Models::Messages::ContentItemSelection that has one ContentItem that is an instance of IMS::LTI::Models::ContentItems::LtiLinkItem. When serialized the payload matches that described in in the aforementioned canvas api docs (see link above).
From here it is unclear what is supposed to happen.
The response from my unsigned POST from the TP to Canvas at the "content_item_return_url" is, on inspection, what seems to be a rendering of the Canvas LMS login page. I'm assuming that means I'm unauthenticated and am being directed to log in. However, the "accept_unsigned" param is true in the original request to the TP. Also, I am including the "data" param, which looks to be a JWT token, as described in the IMS docs:
data=... (Optional)
An opaque value which should be returned by the TP in its response.
So, what does a proper response to the POST to "content_item_return_url" look like?
Next, from the IMS docs it says
A user should always be redirected back to the TC using an auto-submitted form as an HTTP POST request; this return message may, or may not, be signed.
This is confusing; how is one supposed to be returned to the TC via a POST? Is there supposed to be a redirect to the "content_item_return_url" after the POST to the "content_item_return_url"? Does canvas automagically know to send you back once an AJAX call has been made to the "content_item_return_url" in the browser? (Remember I am POSTing here server side).
So currently, after I make the POST with the content-items (which seems unsuccessful ), I am doing an explicit redirect back to the "content_item_return_url" ( which is the only "*_return_url" param I have available ). I get a spinner in the external tool modal in Canvas, however, no data is populated in the LTI link space and I get an error, of which I have attached an image.
Could you please elaborate on the ContentItemSelection flow and it's implementation in Canvas, the expected response to the POST from the TP to the TC for a ContentItemSelectionRequest, and the redirection the is supposed to happen?
Thank you
I'd like to add some more info to this question:
Here is a server log for the POST from my TP to Canvas for the ContentItemSelection, with debugging in the external_content_controller.rb:
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] Processing by ExternalContentController#success as */*
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] Parameters: {"lti_message_type"=>"ContentItemSelection", "lti_version"=>"LTI-1p0", "content_items"=>"{ \"@context\": \"http://purl.imsglobal.org/ctx/lti/v1/ContentItem\", \"@graph\": [ { \"@type\": \"LtiLinkItem\", \"url\": \"http://auth.docker/admin/lti\", \"text\": \"LTI Link\", \"title\": \"My Assignment Link\", \"placementAdvice\": { \"displayWidth\": 147, \"displayHeight\": 184, \"presentationDocumentTarget\": \"embed\" } } ] }", "data"=>"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkZWZhdWx0X2xhdW5jaF91cmwiOiJodHRwOlwvXC9hdXRoLmRvY2tlclwvYWRtaW5cL2x0aVwvY29udGVudF9pdGVtX3NlbGVjdGlvbiJ9.WHnhlzS6m5ilsicjUvBlT32FByjRG18W9UgyokKaEtA", "oauth_consumer_key"=>"key", "oauth_nonce"=>"9fa633c231391527a53d8bbf6c5efd87", "oauth_signature"=>"hEE762LKKa1pPye8MpqQ82VDah0=", "oauth_timestamp"=>"1493305644", "oauth_signature_method"=>"HMAC-SHA1", "oauth_version"=>"1.0", "course_id"=>"1", "service"=>"external_tool_dialog"}
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] IN SUCCESS[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] SQL Course Load (6.7ms) SELECT "courses".* FROM "courses" WHERE (courses.workflow_state<>'deleted') AND "courses"."id" = 1 ORDER BY "courses"."id" ASC LIMIT 1 [development:1 master]
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] IN CONTENT ITEMS FOR CANVAS
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] CONTENT_ITEM[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] #<IMS::LTI::Models::ContentItems::LtiLinkItem:0x007f7d2a1c2e20 @ext_attributes={}, @unknown_attributes={}, @type="LtiLinkItem", @placement_advice=#<IMS::LTI::Models::ContentItemPlacement:0x007f7d2a1bf748 @ext_attributes={}, @unknown_attributes={}, @display_height=184, @display_width=147, @presentation_document_target="embed">, @url="http://auth.docker/admin/lti", @text="LTI Link", @title="My Assignment Link">
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] DEPRECATION WARNING: You are calling a `*_path` helper with the `only_path` option explicitly set to `true`. This option will stop working on path helpers in Rails 5. Simply remove the `only_path: true` argument from your call as it is redundant when applied to a path helper. (called from named_context_url at /usr/src/app/app/controllers/application_controller.rb:321)
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] SQL Account Load (3.1ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = 3 LIMIT 1 [["id", 3]] [development:1 master]
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] SQL Account Load (3.0ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = 1 LIMIT 1 [["id", 1]] [development:1 master]
[- 6ac4247d-92e8-453b-94c5-94d0cf76448b] Rendered external_content/success.html.erb within layouts/application (9.7ms)
One can see that Canvas did create a ContentItem and rendered the success.html.erb. However, I switched to making the request in the browser via AJAX, as prescribed by the spec, and there seem to be CORS issues. Canvas doesn't seem to be responding with any Access-Control headers. Here are the Canvas response headers, running in RAILS_ENV - development.
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Connection:keep-alive
Content-Type:text/html; charset=utf-8
Date:Thu, 27 Apr 2017 15:07:36 GMT
Pragma:no-cache
Server:nginx/1.9.12
Set-Cookie:_normandy_session=QB9b-4JT5V86YaU4HaZBwA+EqCfsuZobceZXgnnRf9H9KvvpfQEY3LASMsAwM8rceLFGiC88QgjUiDlJmgr4nsCvnbkQpWK1YiVLsbLuoZOfQ-Dg6lfwScawGkvvm69Vd_kiu02fwq4NacTOW_EcJUHidrifWiuk3VqQKIUv5ucDw.9V2v3sHRHlfMEIRhCoN1gZ4JYjs.WQIJOA; path=/; HttpOnly
Set-Cookie:_csrf_token=4HYOHkOl1l1CI%2BM0dOiBW06znp70YWBa7RjzlcuQMgq2RVh5DML9KxoXilkx3vIDAdDmzY4WLhSla5mmuuphSw%3D%3D; path=/
Set-Cookie:log_session_id=475ade4c6f598d6928056f1ce8b40981; path=/
Status:200 OK
Transfer-Encoding:chunked
X-Canvas-Meta:o=external_content;n=success;t=Course;i=1;b=650920;m=650920;u=0.07;y=0.01;d=0.04;
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-Request-Context-Id:6ac4247d-92e8-453b-94c5-94d0cf76448b
X-Runtime:0.149933
X-Session-Id:475ade4c6f598d6928056f1ce8b40981
X-UA-Compatible:IE=Edge,chrome=1
X-XSS-Protection:1; mode=block
And I am unable to load the response:
XMLHttpRequest cannot load http://canvas.docker/courses/1/external_content/success/external_tool_dialog. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://auth.docker' is therefore not allowed access.
So no link gets created in the browser and the selection flow is left incomplete.
Continuation from above:
legacy/external_content_success.coffee - handling the response. (A LinkItem has been created with a url in the external_content_controller, see above )
dataReady = function(data, service_id) {
debugger;
var e;e = $.Event("externalContentReady");
e.contentItems = data;
e.service_id = service_id;
debugging the data from above:
- [Object]
- 0:Object
- @type:"ContentItem"
- mediaType:"text/html"
- placementAdvice:Object
- presentationDocumentTarget:"window"
- __proto__:Object
- __proto__:Object
- length:1
- __proto__:Array(0)
Indeed, there is no url, and I get the error - "There was a problem retrieving a valid link from the external tool".
Can someone shed some light as to why the data is not getting returned.
Canvas Developers; would appreciate if this got a response. Please and Thank you.
Bump Canvas Developers anyone seen this?
Hi @ebrohman --
I've gotten this to work (in a very rudimentary test script). My understanding is that the TP --> Canvas POST request containing the ContentItemSelection message should not be done via AJAX, but as a regular form submission. I accomplished this by returning a page containing a regular form, and then automatically submitting that form via javascript. Something like:
<form method="post" action="{{ content_item_return_url }}" id="assignment_selection">
<input type="hidden" name="data" value="{{ data }}"/>
<input type="hidden" name="lti_message_type" value="ContentItemSelection"/>
<input type="hidden" name="lti_version" value="LTI-1p0"/>
<input type="hidden" name="content_items" value="{{ content_items_json }}"/>
</form>
<script>
document.getElementById('assignment_selection').submit();
</script>That allows Canvas to return the next screen in the workflow.
Hope this helps --
Colin
it is 2022.8.30
it works for me!
thanks
Community helpTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign inTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign in