I'm using ember-simple-auth
and ember-simple-auth-token
for allowing users to log into my app. However, when I call Django Rest Framework in the backend with a POST request to authenticate using a username and password, I get a 406 (Not Acceptable) error. This does not happen in the DRF browsable API, so the backend seems to work fine.
I suspect something related to CORS. I use django-cors-headers
in Django, and allow all in my dev environment. I also use django-rest-framework-jwt
and django-rest-framework-json-api
packages, if that matters.
My API shows an OPTIONS and then a POST call being made:
[09/Mar/2016 07:15:54] "OPTIONS /api-token-auth/ HTTP/1.1" 200 0
[09/Mar/2016 07:15:54] "POST /api-token-auth/ HTTP/1.1" 406 114
Response headers:
HTTP/1.0 406 Not Acceptable
Date: Wed, 09 Mar 2016 07:15:54 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Content-Type: application/vnd.api+json
Allow: POST, OPTIONS
Vary: Accept
Request headers:
POST /api-token-auth/ HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 2
Accept: application/json, text/javascript
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/48.0.2564.116 Chrome/48.0.2564.116 Safari/537.36
Content-Type: application/json
Referer: http://localhost:4200/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
The request headers do not show application/vnd.api+json
but application/json
instead. Unfortunately, no matter what I do in Ember is able to resolve that. I've unsuccessfully tried setting the headers to "Accept": "application/vnd.api+json"
for my app's JSONAPIAdapter, and in ENV['ember-simple-auth-token']
.
I managed to solve this, more or less. It is an unfortunate combination of packages that led to some problems with having JSON API spec between Ember and DRF.
First, the overwriting of headers I managed to do in my controllers/login.js
by simply adding the headers as an argument to .authenticate
. Any args get passed to the ember-simple-auth
authenticator. (I did not need to implement my own authenticator, as Pavol suggested in his answer.)
// controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt',
// Set headers to accept JSON API format
headers = {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json'
};
this.get('session').authenticate(authenticator, credentials, headers);
}
}
});
This introduced the next problem: my content type was not actually JSON API spec, so I did need to implement my own authenticator to translate ember-simple-auth-token
's JWT authenticator to produce JSON API spec compatible format. Didn't get it to work, but something like this:
// authenticators/jwt.js
import Base from 'ember-simple-auth-token/authenticators/token';
export default Base.extend({
/**
Returns an object used to be sent for authentication.
@method getAuthenticateData
@return {object} An object with properties for authentication.
*/
// Make sure this is JSON API compatible format.
getAuthenticateData(credentials) {
const authentication = {
// This is apparently not valid JSON API spec, but you get the gist...
'data': [{
[this.identificationField]: credentials.identification,
[this.passwordField]: credentials.password
}]
};
return authentication;
}
});
Now, on the backend, rest_framework_jwt
and rest_framework_json_api
were still not playing well together.
At this point, I decided that it was just a lot simpler to drop the need for JSON API spec on the auth endpoints: Ember's packages did not produce it, and DRF was having trouble parsing it!
So, I reverted everything on the Ember side, having it produce the request according to my original question. On the DRF side, I subclassed rest_framework_jwt
's views and set the parser to DRF's default JSONParser
:
"""
Make the JWT Views ignore JSON API package and use standard JSON.
"""
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken, \
VerifyJSONWebToken
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
class ObtainJSONWebTokenPlainJSON(ObtainJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer, )
class RefreshJSONWebTokenPlainJSON(RefreshJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
class VerifyJSONWebTokenPlainJSON(VerifyJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
Final result: solved by having my API follow JSON API spec everywhere except the token authentication endpoints.