Search code examples
ember.jsdjango-rest-frameworkember-simple-authdjango-cors-headers

406 (Not Acceptable) when POSTing from Ember to DRF API


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'].


Solution

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