LTI XBlock and SameSite

Question for anyone using Django to serve LTI apps for edx.org courses, and thereby using the LTI xblock : have you seen the notice in Chrome about cookies and the SameSite header? If you have the Chrome dev console open when you load an edx.org page that contains an LTI xblock pulling in an external Django app you’ll see something like :

A cookie associated with a cross-site resource at was set without the SameSite attribute. A future release of Chrome will only deliver cookies with cross-site requests if they are set with SameSite=None and Secure. You can review cookies in developer tools under Application>Storage>Cookies and see more details at and .

If you’ve configured Django’s SESSION_COOKIE_SAMESITE = None, Django doesn’t write SameSite=None , it just doesn’t write anything at all. However, by the looks of this recent pull request in the Django repo, Django is being updated to explicitly write the “None” setting:

Just curious if anyone has thoughts on whether we can assume LTI applications served by Django (with this update) will continue running ok in iframes on edx.org or other Open edX-based sites.

Thanks for your thoughts.

– Daniel

Thanks for posting this @DanielMcQ !

Am also concerned about this issue for our clients that use Cross-domain CSRF cookies

I assume Django will backport this to 1.11 since it’s still supported? But we’ll need to upgrade the minor version at least, and get that into ironwood.

Oh… could this be the cause of my problem with LTI while testing Juniper?
And also why it does not work anymore on a recent single server instance of Ironwood I just installed?

I remember seeing your post @DanielMcQ a few weeks / months back. I have a lead to investigate further what is going on…

I found my problem. It wasn’t related to that. An ALB/ELB/AWS/SSL/NGINX combination was the culprit. Changes I made in our production systems and test systems but not on our single server systems using only the vanilla open-release/ironwood.master and open-release/juniper.alpha1.

@DanielMcQ I ran into this issue and posted here.
https://openedx.slack.com/archives/C0GR05YC9/p1585674197001400

This SameSite cookie is effecting LTI Consumer content from rendering in an external LMSs (e.g. Canvas, Blackboard) from pulling in LTI content from our Open edX instance (LTI Provider). It throws an HTTP 403 response because the anonymous user is not authenticated because the csrftoken is not being sent in the HTTP request to https://github.com/edx/edx-platform/blob/open-release/hawthorn.master/lms/urls.py#L244-L252

@DanielMcQ I seemed to get LTI Consumer through Canvas pulling content from our Open edX Provider by doing the following.

I haven’t received any error about not being logged into my edX account from Canvas and the SESSION_COOKIE_NAME doesn’t display the message since Secure=True and SameSite=None for that cookie.

The cookie didn’t specify a SameSite attribute when it was stored and defaulted to “SameSite=Lax” and broke the same rules specified in the SameSiteLax value. The cookie had to have been set with “SameSite=None” to enable third-party usage.

  1. Installed middleware to update SameSite=None for cookies set by Open edX platform. Following directions from the latest release here on how to install the middleware component.
    GitHub - jotes/django-cookies-samesite at v0.5.1
    Install django-cookies-samesite:
pip install django-cookies-samesite

Add the middleware to the top of MIDDLEWARE_CLASSES:

    MIDDLEWARE_CLASSES = (
        'django_cookies_samesite.middleware.CookiesSameSite',
        ...
    )
  1. Updated /edx/app/edxapp/{cms,lms}.env.json files to include the following changes.
{
    "CSRF_COOKIE_SECURE": true,
    "SESSION_COOKIE_SECURE": true,
}
  1. Updated /edx/app/edxapp/edx-platform/lms/envs/private.py file to include the additional changes.
# Setup for django-cookies-samesite
SESSION_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SAMESITE_FORCE_ALL = True

The django-cookies-samesite middleware has a setting if you need to set specific cookies like so, however, I just forced all cookies to set SameSite=None with the force all setting.

SESSION_COOKIE_SAMESITE_KEYS = {'sessionid', 'hideCaptions', 'hide_captions'}

There are additionally places within the edx-platform repo where additional cookies are being set. A code update would need to include the following changes of the secure parameter for this SameSite change to work over LTI.

response.set_cookie( ... , secure=request.is_secure() )

This article kind of gives a better idea of what’s happening with the SameSite cookie change from Chrome and other browsers that are adopting this update.

cc: @jill

Thanks for posting this @Zachary_Trabookis!

I followed these steps on my Juniper sandbox which is an LTI provider. (Note: had to modify lms.envs.production to add the additional settings instead of lms.envs.private, since it’s not a devstack.)

EDIT Please disregard the notes below. These issues were resolved by creating a brand new session.

I didn’t use SESSION_COOKIE_SAMESITE_KEYS, just SESSION_COOKIE_SAMESITE_FORCE_ALL and thought that would be enough, but the sessionid cookie doesn’t get marked SameSite: None. All the others did though:

Name Secure SameSite
JSESSIONID None
csrftoken None
edx-jwt-cookie-header-payload None
edx-jwt-cookie-signature None
edx-user-info None
edxloggedin None
experiments_is_enterprise None
sessionid

However, when using LTI to access the provider sandbox, only one cookie was marked SameSite: None, and the rest were not. LTI is using OAuth to authenticate, so maybe there’s something different there with how cookies get created?

Name Secure SameSite
JSESSIONID None
csrftoken
sessionid

CC @DanielMcQ @sambapete

I haven’t dug into the code to see why the this might be.

EDIT Replacing SESSION_COOKIE_SAMESITE_FORCE_ALL with this setting didn’t make a difference to the sessionid or csrftoken cookies:

SESSION_COOKIE_SAMESITE_KEYS = ['JSESSIONID', 'csrftoken', 'edx-jwt-cookie-header-payload', 'edx-jwt-cookie-signature', 'edx-user-info', 'edxloggedin', 'experiments_is_enterprise', 'sessionid']

@jill We’re using anonymous authentication with LTI Provider. I couldn’t figure out edX authentication with LTI. I added the middleware at the end of the list then moved it to the beginning. Restarted edX services and then it started working from Canvas.

We’re also not using Site Configuration to set the SESSION_COOKIE_NAME but default configuration.

This is where the session cookie gets updated by site configuration.

@jill Do you think the cookies get set everytime you make an LTI provider request? Wonder if you should try a private browser or clear cookies for your Juniper Open EdX site before making another request.

Good call @Zachary_Trabookis, that fixed it. Will update my previous post to reflect this.

1 Like

@jill @DanielMcQ Here is a fix for LTI Provider calls for the SameSite cookie issue for the Hawthorn. I’m sure this could be applied similarly for releases after this release.

Let me know if you see anything wrong.

1 Like

Thanks for that @Zachary_Trabookis! I cherry-picked your commit into my PR, and fixed a couple of places that are different in master.

See edx-platform#23671 – there’s a sandbox linked there so anyone is welcome to try it out.

1 Like

FYI @DanielMcQ @Zachary_Trabookis edX have merged our PR to master, so this fix will be available in Juniper: edx-platform#23671. :tada:!

@DanielMcQ if this resolves the issue you raised, could you mark this post Solved?

1 Like

@jill @Zachary_Trabookis you guys are amazing. My issue was more generic, as I experienced the issue within a simple Django site I created to act as an LTI provider of content to an Open edX LTI consumer… but I think I can use the same fix you came up with for my situation. So I’ll mark as solved!!

2 Likes

@jill @DanielMcQ Thanks for helping me and everyone else in the community benefit from this update.

@jill @DanielMcQ I noticed that recently when I stood up another devstack_docker environment (e.g. master or open-release/juniper.master) while using Chrome that I’m not able to get past the http://localhost:18000/login screen without commenting out this django_cookies_samesite.middleware.CookiesSameSite and reloading the LMS. I think it’s because Chrome is blocking these cookies below (see graphic too). When I remove the middleware it works just fine for devstack_docker.

Blocked EdX Cookies
Getting this error message with following cookies:

  • csrftoken
  • enterprise_customer_uuid
  • sessionid
  • experiments_is_enterprise

The error message says:

This Set-Cookie was blocked because it had the “SameSite=None” attribute but did not have the “Secure” attribute, which is required in order to use “SameSite=None”.

Solution
Here are two options that we could use with devstack_docker to continue to allow login from the http://localhost:18000/login page.

  1. We could remove this middleware in devstack_docker.
    https://github.com/edx/edx-platform/blob/master/lms/envs/common.py#L1491-L1493
  2. So it appears that when were not on a secure site (e.g. devstack_docker, localhost) then we need to set this SameSite cookies to something other than SameSite=None since that requires a secure connection. My recommendation is to set it to Lax since after reading over this post Cookie SameSite dijelaskan  |  Articles  |  web.dev it appears to be the default that browsers go to and is more open to sending the EdX cookies in a request from a third-party site. Anyway let me know what you think. When I set this value it seems to let me login on devstack_docker. I couldn’t login after provisioning a new devstack_docker environment and I remember that we added this recently.
    # ./edx-platform/lms/env/devstack.py
    
    # django-session-cookie middleware
    DCS_SESSION_COOKIE_SAMESITE = 'Lax'
    

1 Like

@jill @DanielMcQ Committed these updates to make devstack_docker LMS login work again since SameSite=Lax cookie attribute works without having secure site.

Fixes for several releases waiting to be merged.

master

open-release/juniper.master

2 Likes

@jill @DanielMcQ Committed these updates to make production LMS login work again since SameSite=Lax cookie attribute works without having secure site. I was getting the following error.

==> /edx/var/log/supervisor/lms-stderr.log <==
[2020-09-24 18:44:19 +0000] [29859] [INFO] POST /user_api/v1/account/login_session/
2020-09-24 18:44:19,956 WARNING 29859 [django.security.csrf] [user None] log.py:228 - Forbidden 
(CSRF cookie not set.): /user_api/v1/account/login_session/

Fixes for several releases waiting to be merged. This is for the edx/configuration repo. I noticed this error when I deployed this to a AWS staging environment and couldn’t login to the LMS or CMS.

master

open-release/juniper.master

1 Like

@jill We’ve upgraded to open-release/maple.3 in production and now we’re running into the similar issue with LTI not allowing the learner to submit an activity and we’re getting a 403 Forbidden call on

I think that it has something to do with the cookie SameSite=Lax setting when viewed from a non top-level navigation (third-party domain) from our LMS. When I view the same course from the LMS I’m able to submit answers and no error occurs. The csrftoken has the same settings of SameSite=Lax and Secure=True.

When viewing through LTI from Canvas I see that the csrftoken cookie is blocked with our LMS Domain (a.k.a. top-level navigation).

This cookie was blocked because it had the "SameSite=Lax’ attribute and the request was made from a different site and was not initiated by a top-level navigation.

With my understanding of SameSite at SameSite cookies explained  |  Articles  |  web.dev it seems like I would want to set all cookies to SameSite=None and Secure=True if possible. The None value means that the first-party context and third-party context when communicating with our LMS will send the cookie.

Picture below shows where that cookie is blocked from calling the LMS content from LTI Canvas course.

Here is what the cookies look like when viewing the course directly within the LMS. Notice that csrftoken for our LMS domain is not filtered out (yellow) and it still has SameSite=Lax and Secure=True which works because the top-level navigation URL is the LMS site.

Also notice there are several other cookies that are not filtered out.

I’m wondering if we need to enable one or both LMS feature flags below and use this middleware edx-platform/middleware.py at open-release/maple.3 · openedx/edx-platform (github.com)

I’m not sure what additional settings need to be made once you enable these features. Currently we don’t have either of these enabled.

ENABLE_CORS_HEADERS = True
ENABLE_CROSS_DOMAIN_CSRF_COOKIE = True

Update
It appears that django_cookies_samesite.middleware.CookiesSameSite was removed when the platform starting using Django > 3.1. Maple indicates that it’s using version 3.2.13.

fix: Don’t use django-cookies-samesite on Django > 3.1 · openedx/edx-platform@7156771 (github.com)

So this leads me to believe that the following settings are not used when that middleware is disabled.

# django-session-cookie middleware
DCS_SESSION_COOKIE_SAMESITE = 'None'
DCS_SESSION_COOKIE_SAMESITE_FORCE_ALL = True

This leads me to believe that SameSite is handled by Django HttpResponse.set_cookie() method with this change Fixed #30862 – Allowed setting SameSite cookies flags to ‘None’. by danidee10 · Pull Request #11894 · django/django (github.com).