How to use curl with Django, csrf tokens and POST requests - django

I'm using curl to test one of my Django forms. The calls I've tried (with errors from each, and over multiple lines for readability):
(1):
curl
-d "{\"email\":\"test#test.com\"}"
--header "X-CSRFToken: [triple checked value from the source code of a page I already loaded from my Django app]"
--cookie "csrftoken=[same csrf value as above]"
http://127.0.0.1:8083/registrations/register/
(with http header and csrftoken in cookie) results in a 400 error with no data returned.
(2):
curl
-d "{a:1}"
--header "X-CSRFToken:[as above]"
--cookie "csrftoken=[as above];sessionid=[from header inspection in Chrome]"
http://127.0.0.1:8083/registrations/register/
(as in (1) but no spaces in header property declaration, and with sessionid in cookie too) results in the same 400 error with no data returned.
(3):
curl
-d "{a:1}"
--header "X-CSRFToken:[as above]"
http://127.0.0.1:8083/registrations/register/
(only http header with X-CSRFToken, no cookie) results in error code 403, with message: CSRF cookie not set.
How can I test my form with curl? What factors am I not considering besides cookie values and http headers?

A mixture of Damien's response and your example number 2 worked for me. I used a simple login page to test, I expect that your registration view is similar. Damien's response almost works, but is missing the sessionid cookie.
I recommend a more robust approach. Rather than manually entering the cookies from other requests, try using curl's built in cookie management system to simulate a complete user interaction. That way, you reduce the chance of making an error:
$ curl -v -c cookies.txt -b cookies.txt host.com/registrations/register/
$ curl -v -c cookies.txt -b cookies.txt -d "email=user#site.com&a=1&csrfmiddlewaretoken=<token from cookies.txt>" host.com/registrations/register/
The first curl simulates the user first arriving at the page with a GET request, and all the necessary cookies are saved. The second curl simulates filling in the form fields and sending them as a POST. Note that you have to include the csrfmiddlewaretoken field in the POST data, as suggested by Damien.

Try:
curl
-d "email=test#test.com&a=1"
http://127.0.0.1:8083/registrations/register/
Notice especially the format of the -d argument.
However, this probably won't work, as your view likely needs a POST request instead of a GET request. Since it will be modifying data, not just returning information.
CSRF protection is only required for 'unsafe' requests (POST, PUT, DELETE). It works by checking the 'csrftoken' cookie against either the 'csrfmiddlewaretoken' form field or the 'X-CSRFToken' http header.
So:
curl
-X POST
-d "email=test#test.com&a=1&csrfmiddlewaretoken={inserttoken}"
--cookie "csrftoken=[as above]"
http://127.0.0.1:8083/registrations/register/
It's also possible to use --header "X-CSRFToken: {token}" instead of including it in the form data.

I worked with curl like this
You have to submit csrftoken in header as X-CSRFToken.
You have to submit form data in JSON format.
Demo,
First we will fetch csrf_token & store in cookie.txt (or cookie.jar as they call it)
$ curl -c cookie.txt http://localhost.com:8000/
cookie.txt content
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
localhost.com FALSE / FALSE 1463117016 csrftoken vGpifQR12BxT07moOohREGmuKp8HjxaE
Next we resend the username, password in json format. (you may send it in normal way). Check the json data escape.
$curl --cookie cookie.txt http://localhost.com:8000/login/ -H "Content-Type: application/json" -H "X-CSRFToken: vGpifQR12BxT07moOohREGmuKp8HjxaE" -X POST -d "{\"username\":\"username\",\"password\":\"password\"}"
{"status": "success", "response_msg": "/"}
$
you can store the returns new csrf_token session cookie in same file or new file (I have stored in same file using option -c.)
$curl --cookie cookie.txt http://localhost.com:8000/login/ -H "Content-Type: application/json" -H "X-CSRFToken: kVgzzB6MJk1RtlVnyzegEiUs5Fo3VRqF" -X POST -d "{\"username\":\"username\",\"password\":\"password\"}" -c cookie.txt
-Content of cookie.txt
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
localhost.com FALSE / FALSE 1463117016 csrftoken vGpifQR12BxT07moOohREGmuKp8HjxaE
#HttpOnly_localhost.com FALSE / FALSE 1432877016 sessionid cg4ooly1f4kkd0ifb6sm9p
When you store new csrf_token & session id cookie in cookie.txt, you can use same cookie.txt across the website.
You am reading cookies from previous request from cookie.txt (--cookie) and writing new cookies from response in same cookie.txt (-c).
Reading & submitting form now works with csrf_token & session id.
$curl --cookie cookie.txt http://localhost.com:8000/home/

Here is how i did it, using the rest framework tutorial
open a browser e.g. chrome then pressing F12 open the developer tab and monitor the Network, login using your user credentials and get your CRSF token from monitoring the POST
then in curl execute:
curl http://127.0.0.1:8000/snippets/ \
-X POST \
-H "Content-Type: application/json" \
-H "Accept: text/html,application/json" \
-H "X-CSRFToken: the_token_value" \
-H "Cookie: csrftoken=the_token_value" \
-u your_user_name:your_password \
-d '{"title": "first cookie post","code": "print hello world"}'
I think its cleaner to not put the token in the body but rather the header using X-CSRFToken

curl-auth-csrf is a Python-based open-source tool capable of doing this for you: "Python tool that mimics cURL, but performs a login and handles any Cross-Site Request Forgery (CSRF) tokens. Useful for scraping HTML normally only accessible when logged in."
This would be your syntax:
echo -n YourPasswordHere | ./curl-auth-csrf.py -i http://127.0.0.1:8083/registrations/register/ -d 'email=test#test.com&a=1' http://127.0.0.1:8083/registrations/register/
This will pass along the POST data as listed, but also to include the password passed via stdin. I assume that the page you visit after "login" is the same page.
Full disclosure: I'm the author of curl-auth-csrf.

To make the Curl–Django communication work, I had to provide
the CSRF token in the X-CSRFToken header field;
the CSRF token in the Cookie header field;
the session identifier in the Cookie header field.
$ curl -v -X PUT -H "X-CSRFToken: {csrf_token}" --cookie "csrftoken={csrf_token};sessionid={session_id}" http://localhost:{port}{path}?{query}
* Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: csrftoken={csrf_token};sessionid={session_id}
> X-CSRFToken: {csrf_token}
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 204 No Content
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 0
< Server-Timing: TimerPanel_utime;dur=159.20299999999975;desc="User CPU time", TimerPanel_stime;dur=70.73100000000032;desc="System CPU time", TimerPanel_total;dur=229.93400000000008;desc="Total CPU time", TimerPanel_total_time;dur=212.03255653381348;desc="Elapsed time", SQLPanel_sql_time;dur=7.846832275390625;desc="SQL 7 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:27:04 GMT
<
* Closing connection 0
Failed attempts
If I omit the CSRF token in the X-CSRFToken header field, I get a 403 (Forbidden) status code:
$ curl -v -X PUT --cookie "csrftoken={csrf_token};sessionid={session_id}" http://localhost:{port}{path}?{query}
* Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: csrftoken={csrf_token};sessionid={session_id}
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 403 Forbidden
< Content-Type: application/json
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 116
< Server-Timing: TimerPanel_utime;dur=79.28900000000283;desc="User CPU time", TimerPanel_stime;dur=10.49199999999928;desc="System CPU time", TimerPanel_total;dur=89.78100000000211;desc="Total CPU time", TimerPanel_total_time;dur=111.31906509399414;desc="Elapsed time", SQLPanel_sql_time;dur=4.807949066162109;desc="SQL 3 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:49:13 GMT
<
* Closing connection 0
{"detail":[{"location":"non_field_errors","message":"CSRF Failed: CSRF token missing.","type":"permission_denied"}]}
If I omit the CSRF token in the Cookie header field, I get a 403 (Forbidden) status code:
$ curl -v -X PUT -H "X-CSRFToken: {csrf_token}" --cookie "sessionid={session_id}" http://localhost:{port}{path}?{query}
* Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: sessionid={session_id}
> X-CSRFToken: {csrf_token}
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 403 Forbidden
< Content-Type: application/json
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 117
< Server-Timing: TimerPanel_utime;dur=81.76699999999926;desc="User CPU time", TimerPanel_stime;dur=10.824999999996976;desc="System CPU time", TimerPanel_total;dur=92.59199999999623;desc="Total CPU time", TimerPanel_total_time;dur=112.99705505371094;desc="Elapsed time", SQLPanel_sql_time;dur=5.406379699707031;desc="SQL 3 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:53:39 GMT
<
* Closing connection 0
{"detail":[{"location":"non_field_errors","message":"CSRF Failed: CSRF cookie not set.","type":"permission_denied"}]}
If I omit the session identifier in the Cookie header field, I get a 401 (Unauthorized) status code:
$ curl -v -X PUT -H "X-CSRFToken: {csrf_token}" --cookie "csrftoken={csrf_token}" http://localhost:{port}{path}?{query}
* Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: csrftoken={csrf_token}
> X-CSRFToken: {csrf_token}
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 401 Unauthorized
< Content-Type: application/json
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 129
< Server-Timing: TimerPanel_utime;dur=21.655999999993014;desc="User CPU time", TimerPanel_stime;dur=4.543999999995663;desc="System CPU time", TimerPanel_total;dur=26.199999999988677;desc="Total CPU time", TimerPanel_total_time;dur=41.02301597595215;desc="Elapsed time", SQLPanel_sql_time;dur=0;desc="SQL 0 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:58:33 GMT
<
* Closing connection 0
{"detail":[{"location":"non_field_errors","message":"Informations d'authentification non fournies.","type":"not_authenticated"}]}

Related

django : don't return cookie for a particular endpoint

I need to return a response from Django without returning a cookie.
I'm trying to implement a webhook client API that requires:
the use of https
response within 5 seconds
no body in the response
no cookies in the response headers
a 401 unauthorised status code for invalid hmac signatures
I'm working on Django 1.10 (soon to be upgraded to 2.x) where the rest of the app is protected by user validation via sessions.
Part of the endpoint view is as follows:
response200 = HttpResponse(status=200)
response401 = HttpResponse(status=401)
response401.close() # attempt not to set cookie
signature = request.META.get('HTTP_WEBHOOK_SIGNATURE')
if not request.method == 'POST':
return response401
if not signature:
return response401
and so on.
However my attempt to avoid setting the session using response401.close() doesn't work. I've also tried del response401['Set-Cookie']see Django docs
The cookie LocalTest... is still set in this curl session:
$ curl -d "param1=value1&param2=value2" \
-H "webhook-signature: $SIGVAL" \
-H "Content-Type: application/x-www-form-urlencoded" \
-X POST http://127.0.0.1:8000/invoices/webhookendpoint \
-w "\n" -v
...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> POST /invoices/webhookendpoint HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.52.1
> Accept: */*
> x-xero-signature: ZSlYlcsLbYmas53uHNrBFiVL0bLbIKetQI6x8JausfA=n
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 27
>
* upload completely sent off: 27 out of 27 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 401 Unauthorized
< Date: Thu, 11 Apr 2019 08:32:50 GMT
< Server: WSGIServer/0.1 Python/2.7.13
< Vary: Cookie
< Content-Type: text/html; charset=utf-8
< Set-Cookie: LocalTest=gwx7jhsshy2qvtct1rmzv86h7xshe6ot; httponly; Path=/
<
* Curl_http_done: called premature == 0
* Closing connection 0
It appears that this works:
# ensure no cookie header is set
del request.session
response200 = HttpResponse(status=200)
response401 = HttpResponse(status=401)
...
as shown in the curl response:
< HTTP/1.0 200 OK
< Date: Thu, 11 Apr 2019 08:49:28 GMT
< Server: WSGIServer/0.1 Python/2.7.13
< Content-Type: text/html; charset=utf-8
<
Naturally, if you go to this endpoint as a logged in user, you will have to log in again.

generate access token using Postman

I have written API using Django REST Frameword and Django oAuth Toolkit for oauth2 authentication and using Postman to test my API authorization process.
I have to send following curl request
curl -X POST -d "grant_type=password&username=<user>&password=<password>" -u "<client_id>:<client_secret" http://127.0.0.1:3333/auth/token/
I can generate access_token simply using Postman Get Access Token window
But I want to do it by sending a request and passing data using request form, so that I could test the API and also generate the documentation for auth.
Now, I can pass user data (username, password) in form-data but how to pass client_id and client_secret?
For a full Postman answer, the way to accomplish this is with a pre-request script. The client id and the client secret are simply encoded with the base64 encoding scheme. Just do this:
Notice that client_id_client_secret is an environment variable. If you don't want to do that, then drop the first line and hard-code your client id and secret into CryptoJS.enc.Utf8.parse('my-trusted-client:mysecret'), where 'my-trusted-client' is the client id and 'mysecret' is the client secret.
Here's the code for copy/paste joy.
let keys = pm.environment.get('client_id_client_secret');
let encoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(keys));
pm.environment.set("base64_client_id_client_secret", encoded);
Now, create a header and include the variable you created:
The value part of that image:
Basic {{base64_client_id_client_secret}}
Now... just Postman bliss.
curl encrypts the value of -u parameter, which we can see using -v (verbose)option.
Therefore, to collect the header's authorization value, use -v once with the curl command. It will print the raw request as following:-
$ curl -X POST -d "grant_type=password&username=<user>&password=<password>" -u "client_id:client_secret" http://127.0.0.1:3000 -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Rebuilt URL to: http://127.0.0.1:3000/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
* Server auth using Basic with user 'client_id'
> POST / HTTP/1.1
> Host: 127.0.0.1:3000
> Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 55
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 55 out of 55 bytes
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 19 May 2018 07:09:35 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
In the above verbose log, we can see the Key Value pairs as
> Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
After collecting these key as "Authorization" and value as "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=", you can use them in headers of the request through postman. "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=" is the encrypted value generated using the -u "client_id:client_secret" option with curl.
Hope this will solve the auth problem using postman request.

django-cors-headers not working when i18n is on

I have a working environment with:
django==1.10
django-rest-framework==3.5.3
djangorestframework-jsonapi==2.1.1
channels (latest)
daphne (latest) instead of gunicorn.
I'm using nginx as a proxy server above daphne, inside a docker environment.
I'm building a separate angular 2 SPA that connects to the above backend and
I'm using django-cors-headers==2.0.2 to allow connections from that web app.
It works with: USE_I18N = False
It works fine when I set Django's USE_I18N = False. When trying to authenticate against the backend, I send a POST request equivalent to:
curl -H "Content-Type: application/vnd.api+json" -X POST -d '{"data": {"type": "obtainJSONWebTokens", "attributes": {"email":"admin#email.com", "password":"password"}}}' http://localhost/api/auth/login/ --verbose
Output from curl:
* Trying ::1...
* Connected to localhost (::1) port 80 (#0)
> POST /api/auth/login/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.49.0
> Accept: */*
> Content-Type: application/vnd.api+json
> Content-Length: 107
>
* upload completely sent off: 107 out of 107 bytes
< HTTP/1.1 200 OK
< Server: nginx/1.11.9
< Date: Mon, 20 Mar 2017 13:00:47 GMT
< Content-Type: application/vnd.api+json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Allow: POST, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: en
< Vary: Accept, Accept-Language, Cookie
<
{"data":{"token":"<token>"}}
* Connection #0 to host localhost left intact
I receive the JWT Token that I am supposed to receive. All works fine.
It fails with: USE_I18N = True
However, the same connection fails when USE_I18N = True.
Output from curl:
* Trying ::1...
* Connected to localhost (::1) port 80 (#0)
> POST /api/auth/login/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.49.0
> Accept: */*
> Content-Type: application/vnd.api+json
> Content-Length: 107
* upload completely sent off: 107 out of 107 bytes
< HTTP/1.1 302 Found
< Server: nginx/1.11.9
< Date: Mon, 20 Mar 2017 12:53:49 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Location: /en/api/auth/login/
< Vary: Cookie
<
* Connection #0 to host localhost left intact
The returned error on the client side is:
XMLHttpRequest cannot load http://localhost/api/auth/login/. Redirect from 'http://localhost/api/auth/login/' to 'http://localhost/en/api/auth/login/' has been blocked by CORS policy: Request requires preflight, which is disallowed to follow cross-origin redirect.
Relevant settings:
INSTALLED_APPS += (
'corsheaders',
)
if DEBUG is True:
CORS_ORIGIN_ALLOW_ALL = True
MIDDLEWARE_CLASSES = (
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.admindocs.middleware.XViewMiddleware',
)
It would seem it's not the client request that fails but the redirection from 'http://localhost/api/auth/login/' to 'http://localhost/en/api/auth/login/', where Django adds the 'en' to the URL.
Can someone shed any light into this?
I've searched for django-cors-headers related issues but none is specific to this apparent incompatibility with I18N. The library works fine without I18N, just not with it on.
EDIT 2017-03-21
Given the limitations stated in the accepted answer, I opted by simply avoiding Django's language URL redirections. While using USE_I18N = True, I completely avoided i18n_patterns in the root URLconf.
In fact, Django Rest Framework states this is a best practice for API clients:
If you want to allow per-request language preferences you'll need to include django.middleware.locale.LocaleMiddleware in your MIDDLEWARE_CLASSES setting.
You can find more information on how the language preference is determined in the Django documentation. For reference, the method is:
First, it looks for the language prefix in the requested URL.
Failing that, it looks for the LANGUAGE_SESSION_KEY key in the current user’s session.
Failing that, it looks for a cookie.
Failing that, it looks at the Accept-Language HTTP header.
Failing that, it uses the global LANGUAGE_CODE setting.
For API clients the most appropriate of these will typically be to use the Accept-Language header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an Accept-Language header for API clients rather than using language URL prefixes.
So, I kept the above settings the same but changed the following in the root URLconf:
urlpatterns += i18n_patterns(
url(_(r'^api/$'), SwaggerSchemaView.as_view(), name='api'),
url(_(r'^api/account/'), include(account_patterns, namespace='account')),
url(_(r'^api/auth/'), include(auth_patterns, namespace='auth')),
url(_(r'^api/'), include('apps.party.api.urls', namespace='parties')),
url(_(r'^api/'), include('apps.i18n.api.urls', namespace='i18n')),
url(_(r'^api-auth/'), include('rest_framework.urls', namespace='rest_framework')),
url(_(r'^admin/'), include(admin_patterns)),
url(_(r'^docs/'), include('apps.docs.urls'))
)
to
urlpatterns += ([
url(_(r'^api/$'), SwaggerSchemaView.as_view(), name='api'),
url(_(r'^api/account/'), include(account_patterns, namespace='account')),
url(_(r'^api/auth/'), include(auth_patterns, namespace='auth')),
url(_(r'^api/'), include('apps.party.api.urls', namespace='parties')),
url(_(r'^api/'), include('apps.i18n.api.urls', namespace='i18n')),
url(_(r'^api-auth/'), include('rest_framework.urls', namespace='rest_framework')),
url(_(r'^admin/'), include(admin_patterns)),
url(_(r'^docs/'), include('apps.docs.urls'))]
)
So, now, doing:
curl -H "Content-Type: application/vnd.api+json" -H "Accept-Language: pt" -X POST -d '{"data": {"type": "obtainJSONWebTokens", "attributes": {"email":"admin#email.com", "password":"password"}}}' http://localhost:8000/api/auth/login/ --verbose
returns the expected response in the requested language (please notice the inclusion of "Accept-Language: pt" in the request above):
* Trying ::1...
* Connected to localhost (::1) port 8000 (#0)
> POST /api/auth/login/ HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.49.0
> Accept: */*
> Content-Type: application/vnd.api+json
> Accept-Language: pt
> Content-Length: 107
>
* upload completely sent off: 107 out of 107 bytes
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Allow: POST, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Vary: Accept, Accept-Language, Cookie
< Content-Language: pt
< Content-Type: application/vnd.api+json
<
{"data": {"token":"<token>"}}
* Connection #0 to host localhost left intact
In essence, you're running into a bug in an older version of the CORS standard.
The original standard basically made it impossible to do local redirects when using prelight requests. See this question on the subject, along with this bug report on the Fetch standard.
In your case this happens with USE_I18N = True because that setting is what triggers the redirects.
Hopefully the fix will soon be implemented by the browsers. (According to the latest report on the Fetch bug it already works in Edge.) In the meantime, this answer suggests some workarounds.
I had the same problem since I did not use the i18n_patterns for all my URLs and one of the URLs that was not inside i18n_patterns returned a 404 response.
I solved it by overwriting the Middleware LocaleMiddleware that imports Django by default.
class CustomLocaleMiddleware(LocaleMiddleware):
def current_urlpattern_is_locale(self, path):
try:
resolver = get_resolver(None).resolve(path)
except Resolver404:
return self.is_language_prefix_patterns_used()
return isinstance(resolver, LocaleRegexURLResolver)
def process_response(self, request, response):
language = translation.get_language()
language_from_path = translation.get_language_from_path(request.path_info)
if (response.status_code == 404 and not language_from_path
and self.current_urlpattern_is_locale(request.path)):
urlconf = getattr(request, 'urlconf', None)
language_path = '/%s%s' % (language, request.path_info)
path_valid = is_valid_path(language_path, urlconf)
if (not path_valid and settings.APPEND_SLASH
and not language_path.endswith('/')):
path_valid = is_valid_path("%s/" % language_path, urlconf)
if path_valid:
script_prefix = get_script_prefix()
language_url = "%s://%s%s" % (
request.scheme,
request.get_host(),
# insert language after the script prefix and before the
# rest of the URL
request.get_full_path().replace(
script_prefix,
'%s%s/' % (script_prefix, language),
1
)
)
return self.response_redirect_class(language_url)
if not (self.is_language_prefix_patterns_used()
and language_from_path):
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
response['Content-Language'] = language
return response

Nginx+uWSGI+Django are returning 502 when big request body and expired session

I have a Django view that process POST request with random size(between 20 char to 30k char). This API is only available for registered users and is validated with a session header. The API works well with my test cases but I notice some 502 in the Nginx log. The error log show this line::
2016/12/26 19:53:15 [error] 1048#0: *72 sendfile() failed (32: Broken pipe) while sending request to upstream, client: XXX.XXX.XXX.XXX, server: , request: "POST /api/v1/purchase HTTP/1.1", upstream: "uwsgi://unix:///opt/project/sockets/uwsgi.sock:", host: "staging.example.com"
After some tests, I managed to recreate this call with a big body request.
curl -XPOST https://staging.example.com/api/v1/purchase \
-H "Accept: application/json" \
-H "token: development-token" \
-H "session: bad-session" \
-i -d '{"receipt-data": "<25677 character string>"}'
HTTP/1.1 100 Continue
HTTP/1.1 502 Bad Gateway
Server: nginx/1.4.6 (Ubuntu)
Date: Mon, 26 Dec 2016 19:54:32 GMT
Content-Type: text/html
Content-Length: 181
Connection: keep-alive
<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.4.6 (Ubuntu)</center>
</body>
</html>
What it seems to happen is that the Django checks that the session is not valid and return the response(403) before the client finish delivers the body.
If I'm correct, is there a way to make Django send that 100 status after checking the headers instead of the Nginx?
If not, is there a more elegant solution than wait for the body before checking the headers?
I've found a statement that adding HTTP header connection:keep-alive to the client should fix this issue. I'll verify it later, but already posting it here, hope it will help someone.

Using "Content-Type:application/json" to post in curl gives HTTP/1.1 400 BAD REQUEST

When I make a post request using the following
curl -i -d "username=rock&password=rock" http://my_VM_IP/api/1.1/json/my_login/
it generates the required response generating a token like this(abridged):
HTTP/1.1 200 OK
Date: Mon, 22 Oct 2012 08:37:39 GMT
Vary: Authorization,Accept-Language,Cookie,Accept-Encoding
Content-Type: text/plain
Transfer-Encoding: chunked
OK{"success": {"my_token": "required_token"}}
But when I try the same including a header as:
curl -i -H "Content-Type:application/json" -d "username=rock&password=rock" http://my_VM_IP/api/1.1/json/my_login/
it gives me the following error:
HTTP/1.1 400 BAD REQUEST
Date: Mon, 22 Oct 2012 11:12:04 GMT
Vary: Authorization,Accept-Language,Cookie,Accept-Encoding
***Content-Type: text/plain***
Content-Language: en-us
Connection: close
Transfer-Encoding: chunked
Bad Request
I dont understand why this happens. And also why does the content-Type show text/plain, I also tried looking at some other questions like Why Setting POST Content-type:"Application/Json" causes a "Bad Request" on REST WebService? . It also addresses the same problem I have. Following the answer in that I tried giving the data in various formats as
{"username":"rock", "password":"rock"}
but without success. Thanks in advance.
By using -H "Content-Type:application/json" you're setting the Content-Type header for your request. The response will still return whatever your view tells it to return.
To return a response with Content-Type application/json, use something along these lines:
import json
from django.http import HttpResponse
def json_response(return_vars):
'JSON-encodes return_vars returns it in an HttpResponse with a JSON mimetype'
return HttpResponse(json.dumps(return_vars), content_type = "application/json")
#Usage: return json_response({'admin_token': admin_api_token.token})
You were close, but you need to send it as a JSON format via curl:
curl -i -H "Content-Type:application/json" -d '{"username":"rock", "password":"rock"}'
("password","admin" should be "password":"admin")
If that's not working, try:
curl --dump-header - -H "Accept:application/json" -H "Content-Type:application/json" -X POST --data '{"username": "admin", "password": "admin"}' http://my_VM_IP/api/1.1/json/my_login/
When you set -H parameter of curl command, you specify content type of request. Content type of response, that you see in response, is set on the server. In WSGI application you need to specify 'content-type' and 'content-length' manually. Some of framework provide utility method to return JSON responses (for example, jsonify method in Flask).