CORS errors after patch 10.2.2

Hi! I have run into multiple CORS errors since the release of 10.2.2. All subdomains of the LMS should be accepted, but I’m having trouble with all MFA’s. I have configured all MFA’s to use a subdomain of tutor. In this case it is local.overhang.io and the MFA is located at tfp.local.overhang.io

To be able to access http://local.overhang.io/login_refresh to receive an JWT-cookie I need to add the following to openedx-common-settings:

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = “http://{{TFP_HOST}}”

TFP_HOST is tfp.local.overhang.io
This produces the following headers:

HTTP/1.1 401 Unauthorized
Server: nginx
Date: Mon, 12 Oct 2020 17:13:52 GMT
Content-Type: application/json
Content-Length: 14
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Content-Language: en
Vary: Accept-Language, Origin, Cookie

Which means I can log in and use the service as expected. However, the MFA relies on content from the rest-api, but accessing the rest-api produces these headers:

HTTP/1.1 200 OK
Server: nginx
Date: Mon, 12 Oct 2020 17:22:19 GMT
Content-Type: application/json
Content-Length: 81
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Access-Control-Allow-Credentials: true
Allow: GET, HEAD, OPTIONS
Content-Language: en
Vary: Accept-Language, Origin, Cookie
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, use-jwt-cookie
Access-Control-Max-Age: 86400

The interesting part is:

Access-Control-Allow-Origin: http://tfp.local.overhang.io &
Access-Control-Allow-Credentials: true

Because they appear twice in the header file, they are blocked by cors. This is the error:

Access to XMLHttpRequest at ‘http://local.overhang.io/api/courses/v1/courses/?page=1&page_size=10&limit=10&fields=uuid,id,key,key_for_reruns,title,modified,editors,course_run_statuses,course_runs,image,short_description,full_description,start,seats,min_effort,max_effort,estimated_hours,status&editable=1&exclude_utm=1’ from origin ‘http://tfp.local.overhang.io’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header contains multiple values ‘http://tfp.local.overhang.io, http://tfp.local.overhang.io’, but only one is allowed.

By removing the following code from the ‘cors-patch’, everything works as normal. Because you are not relying on NGINX for cors-handling

Blockquote # CORS configuration
add_header ‘Access-Control-Allow-Origin’ ‘$allow_origin’;
add_header ‘Access-Control-Allow-Credentials’ ‘true’;
sampled from edx.org
add_header ‘Access-Control-Allow-Headers’ ‘accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, use-jwt-cookie’;
add_header ‘Access-Control-Max-Age’ 86400;

I have tried leaving all settings blank to let NGINX handle everything, but that did not work for me.
Has anyone else run into similar issues? All help is greatly appreciated!

Regards

I believe the Access-Control-Allow-Origin appears twice because you added CORS_ORIGIN_WHITELIST = “http://{{TFP_HOST}}”. Did you try to simply remove that part?

I have the same problem, and it is very annoying.
I’m trying to implement Front-app-publisher and Frontend-app-logistration, with using a plugins. Here an example CORS issue.

and this one after login:

I tried all settings like @SigmundGranaas, but same issue unfortunately.
Regards,
Murat

Thanks for the quick response!
Yes I have tried removing only that part. If I do this, I can no longer access /login-refresh and the MFA simply renders a blank page.

What /login-refresh url are you talking about @SigmundGranaas? This url does not exist in Open edX, as far as I understand.

Sorry, I meant local.overhang.io/login_refresh. This is the REFRESH_ACCESS_TOKEN_ENDPOINT.

@SigmundGranaas I do not understand what is REFRESH_ACCESS_TOKEN_ENDPOINT. This variable does not exist anywhere in the Open edX codebase (unless I’m mistaken).

The fact that you cannot access /login_refresh should not have anything to do with CORS_ORIGIN_WHITELIST or CORS_ALLOW_CREDENTIALS – that is, according to the code that I am reading. So start by removing these items, then we’ll figure out how to resolve the remaining issues. You write:

What does it mean that “it didn’t work for you”? Did you get a CORS error? an HTTP 509 error? Did cows fall from the sky and crash your computer? Was there an error message and/or a stacktrace somewhere?

@regis Sorry for the lack of documentation provided in the original post

I am running tutor 10.2.2, the MFA works on all previous juniper releases.

REFRESH_ACCESS_TOKEN_ENDPOINT

This is the env variable the MFA uses to specify the token endpoint, usually http://LMS_HOST/login_refresh.

This url correspond to these lines of code on the openedx platform:
https://github.com/edx/edx-platform/blob/af958ada750d6634c13858f1bebfe16422cedd3a/openedx/core/djangoapps/user_authn/urls_common.py#L49-L50

https://github.com/edx/edx-platform/blob/3ebf068ff3dff6b54cecdefccefcfe4cea269d46/openedx/core/djangoapps/user_authn/views/login.py#L480-L491

https://github.com/edx/edx-platform/blob/f605ce37631641904ce238f6c4b0f3c8c55840e8/lms/static/js/demographics_collection/DemographicsCollectionModal.jsx#L30-L71

I am not a developer working on or with the @edx/frontend-platform package, so my understanding of the authentication process is not very good. My current understanding is that that all MFA’s (using the edx-frontend react template) are using the frontend-platform package to authenticate the app. This works with the http://LMS_HOST/login_refresh endpoint. My understanding is that the MFA needs to make contact with this endpoint to proceed operation, but it does not need to be authenticated(further down you can see a response: 401 unauthenticated because we are not allowed to transfer jwt-cookies in some cases). In this case you would receive or create an anonymous cookie(I am not quite sure if it is generated by the server or the client.) which is stored by the browser. If you log in through the MFA, you will rececive a jwt-cookie(hence the need to be on the same domain) from the server which will be used as authentication when using services from the edx-platform(including discovery) like the rest-api. The issues I have encountered are the same regardless if I am authenticated or anonymous.

The MFA we are running is a general purpose marketing site for edx/tutor created with the template: GitHub - openedx/frontend-template-application: A template repository for creating Open edX frontend applications. 💿➡️📀. Currently we are building production images which is run in an nginx container. This is added by a plugin. All traffic to tfp.local.overhang.io is forwarded from the master nginx container to the container running the MFA. If this is unclear I can explain further.

What have I tried:
using the cors-lms patch point like in your gradebook plugin: https://github.com/overhangio/tutor-gradebook/blob/master/tutorgradebook/patches/cors-lms. I don’t know exactly what this patch does, but using it with my domain does not make a difference.

Running the plugin with different combinations of openedx-common-settings:
with and without all of the following lines as well as all combinations of these lines:

from corsheaders.defaults import default_headers as corsheaders_default_headers // this is now handled by NGINX

CORS_ALLOW_HEADERS = corsheaders_default_headers + (
‘use-jwt-cookie’,
)

FEATURES[‘ENABLE_CORS_HEADERS’] = True

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = “{{TFP_HOST}}”

ACCESS_CONTROL_ALLOW_ORIGIN = “{{TFP_HOST}}” // I don’t think this actually does anything

these are the settings I used to deal with CORS before it was handled by NGINX. Now the different combinations produce a variation of results which I have posted below. I will focus on the first two

There are two scenarios:

1# No cors related settings in edx-common-settings.

In theory, NGINX should recognize that this is a subdomain and provide the correct headers in the response when posting to local.overhang.io/login_refresh

When trying to retrieve a JWT-cookie from /login_refresh from the MFA:

Log from chrome browser:

Access to XMLHttpRequest at ‘http://local.overhang.io/login_refresh’ from origin ‘http://tfp.local.overhang.io’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

request sent and received from MFA to local.overhang.io/login_refresh:

Request URL: http://local.overhang.io/login_refresh
Referrer Policy: no-referrer-when-downgrade

response:
Connection: keep-alive
Content-Language: en
Content-Length: 14
Content-Type: application/json
Date: Tue, 13 Oct 2020 07:49:45 GMT
Server: nginx
Vary: Accept-Language, Origin, Cookie
X-Frame-Options: SAMEORIGIN

request:
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: ajs_anonymous_id=%22f3d83a2a-065b-456a-a0de-14fa4273b877%22; amplitude_idundefinedoverhang.io=eyJvcHRPdXQiOmZhbHNlLCJzZXNzaW9uSWQiOm51bGwsImxhc3RFdmVudFRpbWUiOm51bGwsImV2ZW50SWQiOjAsImlkZW50aWZ5SWQiOjAsInNlcXVlbmNlTnVtYmVyIjowfQ==; experiments_is_enterprise=false; edxloggedin=true; edx-user-info=“{"username": "SIGII"\054 "header_urls": {"account_settings": "http://local.overhang.io/account/settings\”\054 "learner_profile": "http://local.overhang.io/u/SIGII\“\054 "logout": "http://local.overhang.io/logout\”\054 "resume_block": "http://local.overhang.io/user_api/v1/account/registration/\“}\054 "version": 1}”; openedx-language-preference=en; csrftoken=XcYcV3n5B3kSqZo7mvi6zfaWVUpimWKEBKSD0ORBDjZgzWvlbaa8PogUxs47lgNn; ajs_user_id=6; amplitude_id_a9ce93900ead1deeb18326280d23af97overhang.io=eyJkZXZpY2VJZCI6IjJjZDFkNWI4LWExMGQtNDYxMS04MGYxLWIwNGNmM2U0NmUyNVIiLCJ1c2VySWQiOiI2Iiwib3B0T3V0IjpmYWxzZSwic2Vzc2lvbklkIjoxNjAyNTE2MTQ5MzcyLCJsYXN0RXZlbnRUaW1lIjoxNjAyNTIzNTg5MTczLCJldmVudElkIjo2MTMsImlkZW50aWZ5SWQiOjIsInNlcXVlbmNlTnVtYmVyIjo2MTV9
DNT: 1
Host: local.overhang.io
Origin: http://tfp.local.overhang.io
Referer: http://tfp.local.overhang.io/
USE-JWT-COOKIE: true
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

At this point, the request to refresh_login has been blocked by cors headers not being present. This results in the MFA not loading, simply producing a white page with the error message in the console.

LMS logs:

lms_1 | 2020-10-13 10:54:12,087 WARNING 9 [edx_rest_framework_extensions.auth.jwt.middleware] [user None] middleware.py:244 - Both JWT auth cookies missing. JWT auth cookies will not be reconstituted.
lms_1 | 172.18.0.1 - - [13/Oct/2020:10:54:12 +0000] “POST /login_refresh HTTP/1.0” 401 14 “http://tfp.local.overhang.io/” “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36”

#2 with CORS_ALLOW_CREDENTIALS = True and CORS_ORIGIN_WHITELIST = “http://{{TFP_HOST}}” in openedx-common-settings:

No log output in console when acessing /login_refresh because it succeeds. Here is the request and the response:

Request information for the login_refresh endpoint

Request URL: http://local.overhang.io/login_refresh
Request Method: POST
Status Code: 401 Unauthorized
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

response:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Connection: keep-alive
Content-Language: en
Content-Length: 14
Content-Type: application/json
Date: Tue, 13 Oct 2020 07:55:56 GMT
Server: nginx
Vary: Accept-Language, Origin, Cookie
X-Frame-Options: SAMEORIGIN

request:
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: ajs_anonymous_id=%22f3d83a2a-065b-456a-a0de-14fa4273b877%22; amplitude_idundefinedoverhang.io=eyJvcHRPdXQiOmZhbHNlLCJzZXNzaW9uSWQiOm51bGwsImxhc3RFdmVudFRpbWUiOm51bGwsImV2ZW50SWQiOjAsImlkZW50aWZ5SWQiOjAsInNlcXVlbmNlTnVtYmVyIjowfQ==; experiments_is_enterprise=false; edxloggedin=true; edx-user-info=“{"username": "SIGII"\054 "header_urls": {"account_settings": "http://local.overhang.io/account/settings\”\054 "learner_profile": "http://local.overhang.io/u/SIGII\“\054 "logout": "http://local.overhang.io/logout\”\054 "resume_block": "http://local.overhang.io/user_api/v1/account/registration/\“}\054 "version": 1}”; openedx-language-preference=en; csrftoken=XcYcV3n5B3kSqZo7mvi6zfaWVUpimWKEBKSD0ORBDjZgzWvlbaa8PogUxs47lgNn; ajs_user_id=6; amplitude_id_a9ce93900ead1deeb18326280d23af97overhang.io=eyJkZXZpY2VJZCI6IjJjZDFkNWI4LWExMGQtNDYxMS04MGYxLWIwNGNmM2U0NmUyNVIiLCJ1c2VySWQiOiI2Iiwib3B0T3V0IjpmYWxzZSwic2Vzc2lvbklkIjoxNjAyNTE2MTQ5MzcyLCJsYXN0RXZlbnRUaW1lIjoxNjAyNTIzNTg5MTczLCJldmVudElkIjo2MTMsImlkZW50aWZ5SWQiOjIsInNlcXVlbmNlTnVtYmVyIjo2MTV9
DNT: 1
Host: local.overhang.io
Origin: http://tfp.local.overhang.io
Referer: http://tfp.local.overhang.io/
USE-JWT-COOKIE: true
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

At this point the page will load because the request to login_refresh succeeds and you will receive an anonymous id because you are not logged in. The MFA relies on a succesfull response from this endpoint, otherwise it will not load. The part about the duplicate Access-Control-Allow-Credentials: true and Access-Control-Allow-Origin: http://tfp.local.overhang.io only appear in, and blocks SUBSEQUENT responses. After the page has loaded, it will request all courses from the courses api endpoint: http://local.overhang.io/api/courses/v1/courses this request will have a response from the server containing duplicate Access-control-allow entries. THIS makes sense, considering I specify whitelist and allow credentials in my openedx-common-settings config file in my plugin.

log from chrome console:

Access to XMLHttpRequest at ‘http://local.overhang.io/api/courses/v1/courses/?page=1&page_size=10&limit=10&fields=uuid,id,key,key_for_reruns,title,modified,editors,course_run_statuses,course_runs,image,short_description,full_description,start,seats,min_effort,max_effort,estimated_hours,status&editable=1&exclude_utm=1’ from origin ‘http://tfp.local.overhang.io’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header contains multiple values ‘http://tfp.local.overhang.io, http://tfp.local.overhang.io’, but only one is allowed.

Request information for the courses endpoint

Request:
Request URL: http://local.overhang.io/api/courses/v1/courses/?page=1&page_size=10&limit=10&fields=uuid,id,key,key_for_reruns,title,modified,editors,course_run_statuses,course_runs,image,short_description,full_description,start,seats,min_effort,max_effort,estimated_hours,status&editable=1&exclude_utm=1
Referrer Policy: no-referrer-when-downgrade

response:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, use-jwt-cookie
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Access-Control-Max-Age: 86400
Allow: GET, HEAD, OPTIONS
Connection: keep-alive
Content-Language: en
Content-Length: 81
Content-Type: application/json
Date: Tue, 13 Oct 2020 08:20:21 GMT
Server: nginx
Vary: Accept-Language, Origin, Cookie
X-Frame-Options: SAMEORIGIN

request:
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: keep-alive
DNT: 1
Host: local.overhang.io
Origin: http://tfp.local.overhang.io
Referer: http://tfp.local.overhang.io/courses
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

LMS logs
/login_refresh

lms_1 | 2020-10-13 10:25:12,224 WARNING 11 [edx_rest_framework_extensions.auth.jwt.middleware] [user None] middleware.py:244 - Both JWT auth cookies missing. JWT auth cookies will not be reconstituted.
lms_1 | 172.18.0.1 - - [13/Oct/2020:10:25:12 +0000] “POST /login_refresh HTTP/1.0” 401 14 “http://tfp.local.overhang.io/” “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36”

/courses

| 2020-10-13 10:42:23,233 INFO 10 [tracking] [user None] logger.py:49 - {“ip”: “172.18.0.1”, “username”: “”, “referer”: “http://tfp.local.overhang.io/”, “context”: {“course_id”: “”, “user_id”: null, “org_id”: “”, “path”: “/api/courses/v1/courses/”}, “agent”: “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36”, “event”: “{"POST": {}, "GET": {"page_size": ["10"], "editable": ["1"], "exclude_utm": ["1"], "fields": ["uuid,id,key,key_for_reruns,title,modified,editors,course_run_statuses,course_runs,image,short_description,full_description,start,seats,min_effort,max_effort,estimated_hours,status"], "limit": ["10"], "page": ["1"]}}”, “accept_language”: “nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5”, “time”: “2020-10-13T10:42:23.232886+00:00”, “host”: “local.overhang.io”, “event_type”: “/api/courses/v1/courses/”, “page”: null, “event_source”: “server”}

What I find curious is the fact that the duplicate CORS headers does not appear when accessing login_refresh, only in subsequent requests to the api.

#3 With all settings enabled

Request URL: http://local.overhang.io/login_refresh
Request Method: POST
Status Code: 401 Unauthorized
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Connection: keep-alive
Content-Language: en
Content-Length: 14
Content-Type: application/json
Date: Tue, 13 Oct 2020 10:19:04 GMT
Server: nginx
Vary: Accept-Language, Origin, Cookie
X-Frame-Options: SAMEORIGIN
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: csrftoken=XcYcV3n5B3kSqZo7mvi6zfaWVUpimWKEBKSD0ORBDjZgzWvlbaa8PogUxs47lgNn; ajs_user_id=6; ajs_anonymous_id=%22f3d83a2a-065b-456a-a0de-14fa4273b877%22; amplitude_idundefinedoverhang.io=eyJvcHRPdXQiOmZhbHNlLCJzZXNzaW9uSWQiOm51bGwsImxhc3RFdmVudFRpbWUiOm51bGwsImV2ZW50SWQiOjAsImlkZW50aWZ5SWQiOjAsInNlcXVlbmNlTnVtYmVyIjowfQ==; experiments_is_enterprise=false; amplitude_id_a9ce93900ead1deeb18326280d23af97overhang.io=eyJkZXZpY2VJZCI6IjAwODQyZDBlLTgwZmYtNDAxZS04YWZlLTZmNzUxZmM0MThkZlIiLCJ1c2VySWQiOiI2Iiwib3B0T3V0IjpmYWxzZSwic2Vzc2lvbklkIjoxNjAyNTgzODcwNTQ0LCJsYXN0RXZlbnRUaW1lIjoxNjAyNTg0MjU0NzMwLCJldmVudElkIjoxMzEsImlkZW50aWZ5SWQiOjcsInNlcXVlbmNlTnVtYmVyIjoxMzh9
DNT: 1
Host: local.overhang.io
Origin: http://tfp.local.overhang.io
Referer: http://tfp.local.overhang.io/
USE-JWT-COOKIE: true
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

#4 With only ENABLE_CORS Header and when adding jwt cookie to allowed header(They provide the same result)

Request URL: http://local.overhang.io/login_refresh
Referrer Policy: no-referrer-when-downgrade
Connection: keep-alive
Content-Language: en
Content-Length: 14
Content-Type: application/json
Date: Tue, 13 Oct 2020 10:05:46 GMT
Server: nginx
Vary: Accept-Language, Origin, Cookie
X-Frame-Options: SAMEORIGIN
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: csrftoken=XcYcV3n5B3kSqZo7mvi6zfaWVUpimWKEBKSD0ORBDjZgzWvlbaa8PogUxs47lgNn; ajs_user_id=6; ajs_anonymous_id=%22f3d83a2a-065b-456a-a0de-14fa4273b877%22; amplitude_idundefinedoverhang.io=eyJvcHRPdXQiOmZhbHNlLCJzZXNzaW9uSWQiOm51bGwsImxhc3RFdmVudFRpbWUiOm51bGwsImV2ZW50SWQiOjAsImlkZW50aWZ5SWQiOjAsInNlcXVlbmNlTnVtYmVyIjowfQ==; experiments_is_enterprise=false; amplitude_id_a9ce93900ead1deeb18326280d23af97overhang.io=eyJkZXZpY2VJZCI6IjAwODQyZDBlLTgwZmYtNDAxZS04YWZlLTZmNzUxZmM0MThkZlIiLCJ1c2VySWQiOiI2Iiwib3B0T3V0IjpmYWxzZSwic2Vzc2lvbklkIjoxNjAyNTc2ODQ4MjM0LCJsYXN0RXZlbnRUaW1lIjoxNjAyNTc4MjU3MzgzLCJldmVudElkIjo2MywiaWRlbnRpZnlJZCI6Mywic2VxdWVuY2VOdW1iZXIiOjY2fQ==
DNT: 1
Host: local.overhang.io
Origin: http://tfp.local.overhang.io
Referer: http://tfp.local.overhang.io/courses
USE-JWT-COOKIE: true
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

With all setting enabled, this is a wierd case that I have not been able to reproduce. It The request method is OPTIONS(on the others it is post). When it is used, headers from the nginx config is included in the /login_refresh response, which is great! If I would be able to reproduce this.

Request URL: http://local.overhang.io/login_refresh
Request Method: OPTIONS
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Credentials: true
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, use-jwt-cookie
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, use-jwt-cookie
Access-Control-Allow-Methods: DELETE, GET, OPTIONS, PATCH, POST, PUT
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Access-Control-Allow-Origin: http://tfp.local.overhang.io
Access-Control-Max-Age: 86400
Access-Control-Max-Age: 86400
Connection: keep-alive
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Oct 2020 10:01:43 GMT
Server: nginx
Vary: Origin, Cookie
Accept: /
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5
Access-Control-Request-Headers: use-jwt-cookie
Access-Control-Request-Method: POST
Connection: keep-alive
Host: local.overhang.io
Origin: http://tfp.local.overhang.io
Referer: http://tfp.local.overhang.io/courses
Sec-Fetch-Mode: cors
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

To summarize:

I will focus on the first two scenarios.

1. With nothing specified in openedx-common-settings:

login_refresh request: login_refresh request is blocked because there are no CORS header (In theory, NGINX should provide these)
subsequent requests: never requested, because the MFA does not load if login_refresh is blocked

Note: no “Post” or “Get” method is present in the request

2. With CORS_ALLOW_CREDENTIALS = True and CORS_ORIGIN_WHITELIST = “http://{{TFP_HOST}}” in openedx-common-settings:

login_refresh request: login refresh is not blocked, because it contains a SINGLE entry for CREDENTIALS and Allow Origin(which I believe comes from openedx-common-settings) which does not cause any CORS errors.
subsequent requests: all subsequent requests to local.overhang.io/api/v1/… is blocked because there are now duplicate entries for Allow Origin and Allow Credentials(one from openedx-common-settings and one from NGINX).

It does not look like NGINX appends headers to the request to /login_refresh, only to the subsequent requests. I have no idea how and why this would happen.

Note: Post method is present in the request

When the “OPTION” method was present, all headers were included from the start. However, this only occured once, and I was not able to reproduce it.

If I remove these CORS headers from lms.conf and reload nginx:

CORS configuration
add_header ‘Access-Control-Allow-Origin’ ‘$allow_origin’;
add_header ‘Access-Control-Allow-Credentials’ ‘true’;
sampled from edx.org
add_header ‘Access-Control-Allow-Headers’ ‘accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, use-jwt-cookie’;
add_header ‘Access-Control-Max-Age’ 86400;

Everything works as expected, because it only contains a single pair of headers from the CORS_ORIGIN_WHITELIST setting, but this is circumventing the problem as it is no longer uses the $allow_origin method.

Sorry again for the sloppy explanation in the original post, I hope this makes everything a bit more clear.

Thanks for the detailed explanations @SigmundGranaas. I realize that setting the access headers via Nginx is inconvenient. So I made a change to Tutor that gets rid of the automatic setting via Nginx: https://github.com/overhangio/tutor/commit/2f7742b099a98d5c9101bacfd9ce703a46e62433

This change is yet unpublished, but you can get it by running Tutor from the master branch.

So this is what you should do:

  1. Install Tutor from source, from the master branch
  2. Get rid of the cors-lms patch, which was in any case useless (I removed it in the gradebook app).
  3. Add a “openedx-lms-production-settings” patch to your plugin:
CORS_ORIGIN_WHITELIST.append("{{ TFP_HOST }}")

This should add access header to your requests to the LMS. If it doesn’t, we’ll investigate further.

Notice the .append as well as the lack of scheme (“http/https”).

2 Likes

Thanks for the response @regis! I am testing the changes as we speak, and will post an update with the result. I like the idea of handling CORS with NGINX, and I see no reason as to why it does not work. Is it possible to make the CORS handling in NGINX optional?

I’d rather have one or the other, but not both. And it’s just much simpler to do it at the application level than in Nginx.

This resolved my issue. With no NGINX cors config everything works as it used to. And yes, only having one source of cors-control is probably for the best.

Awesome. So I’ll make a new Tutor release soon.

Fantastic! Thanks for all the help!