Summary
When Open edX is used as an LTI 1.1 tool provider and content is launched inside an iframe on the consumer LMS (verified with Canvas), the initial launch renders correctly, but any subsequent interactive POST (e.g. submitting a problem) is rejected by Django's CSRF middleware with:
Forbidden (CSRF cookie not set.): /courses/.../xblock/.../handler/xmodule_handler/problem_check
The same content works if launched in a new tab / top-level window.
Steps to reproduce
- Configure an Open edX instance as an LTI 1.1 provider (default settings, no changes to
SESSION_COOKIE_SAMESITE / CSRF_COOKIE_SAMESITE).
- In Canvas, add an external tool pointing to a problem component or subsection in the Open edX course.
- Launch the tool inside Canvas (iframe, default behavior).
- Attempt to submit an answer.
Expected
The problem submission succeeds and (if the component is graded) the score is passed back to Canvas.
Actual
POST to the xblock handler returns HTTP 403. LMS logs:
WARNING django.security.csrf ... Forbidden (CSRF cookie not set.): /event
WARNING django.security.csrf ... Forbidden (CSRF cookie not set.): /courses/<course>/xblock/<block>/handler/xmodule_handler/problem_check
[pid: 30] POST /event => 403
[pid: 31] POST /courses/<course>/xblock/<block>/handler/xmodule_handler/problem_check => 403
Root cause
lti_launch (lms/djangoapps/lti_provider/views.py) is @csrf_exempt, so the initial launch POST from Canvas works.
- Subsequent XBlock handler POSTs are standard CSRF-protected views.
- Django defaults are
SESSION_COOKIE_SAMESITE = 'Lax' and CSRF_COOKIE_SAMESITE = 'Lax' (lms/envs/common.py does not override them; it only defines DCS_SESSION_COOKIE_SAMESITE).
- Browsers do not send
Lax cookies on cross-site POSTs from the Canvas iframe, so the CSRF cookie is absent and Django rejects the request.
Environment
- Open edX master sandbox
- LTI version: 1.1
- Consumer LMS: Canvas
- Browser: Firefox 149 (macOS 10.15)
Related code
lms/djangoapps/lti_provider/views.py (lti_launch view, @csrf_exempt + @add_p3p_header)
lms/envs/common.py (DCS_SESSION_COOKIE_SAMESITE exists, but SESSION_COOKIE_SAMESITE / CSRF_COOKIE_SAMESITE do not)
lms/djangoapps/lti_provider/users.py (require_user_account enforcement)
Summary
When Open edX is used as an LTI 1.1 tool provider and content is launched inside an iframe on the consumer LMS (verified with Canvas), the initial launch renders correctly, but any subsequent interactive POST (e.g. submitting a problem) is rejected by Django's CSRF middleware with:
The same content works if launched in a new tab / top-level window.
Steps to reproduce
SESSION_COOKIE_SAMESITE/CSRF_COOKIE_SAMESITE).Expected
The problem submission succeeds and (if the component is graded) the score is passed back to Canvas.
Actual
POST to the xblock handler returns HTTP 403. LMS logs:
Root cause
lti_launch(lms/djangoapps/lti_provider/views.py) is@csrf_exempt, so the initial launch POST from Canvas works.SESSION_COOKIE_SAMESITE = 'Lax'andCSRF_COOKIE_SAMESITE = 'Lax'(lms/envs/common.pydoes not override them; it only definesDCS_SESSION_COOKIE_SAMESITE).Laxcookies on cross-site POSTs from the Canvas iframe, so the CSRF cookie is absent and Django rejects the request.Environment
Related code
lms/djangoapps/lti_provider/views.py(lti_launch view,@csrf_exempt+@add_p3p_header)lms/envs/common.py(DCS_SESSION_COOKIE_SAMESITEexists, butSESSION_COOKIE_SAMESITE/CSRF_COOKIE_SAMESITEdo not)lms/djangoapps/lti_provider/users.py(require_user_accountenforcement)