@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.