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 ofgunicorn
.
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 yourMIDDLEWARE_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 anAccept-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