Related
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'].
Implement your own authenticator which sets up headers used during authentication request:
// your-app/authenticators/your-custom-authenticator.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend({
/**
* your backend authentication endpoint
* #overrides
*/
serverTokenEndpoint: `https://your.authentication.endpoint.sth/login`,
/**
* Makes a request to the authentication server.
* This is what you need to override to customize your headers
* set up for purposes of authentication.
* #overrides
*/
makeRequest(url, data) {
const options = {
url: url,
data: data,
type: 'GET',
dataType: 'json',
accept: 'application/vnd.api+json',
headers: {
"Content-Type": 'application/vnd.api+json'
}
};
return Ember.$.ajax(options);
}
});
Refer to this custom authenticator in your (login) route/controller/wherever you need:
this.get('session').authenticate('authenticator:yourCustomAuthenticator', username, password).then(() => {
// success, redirect, as you like..
})
Take a look at the Authenticators sections of ember-simple-auth docs to choose a parent authenticator as close to your needs as you need: ember-simple-auth - Authenticators
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.
You should be able to explicitly set content-type in your adapter:
export default DS.JSONAPIAdapter.extend({
// set content-type upon every ajax request
ajax: function(url, type, hash){
hash = hash || {} ;
hash.headers = hash.headers || {};
hash.headers['Content-Type'] = 'application/vnd.api+json';
return this._super(url, type, hash);
}
});
Does it solve your problem?
i have an app running over Sencha Touch. It makes a POST request to a Django server with some data from a form. This contains textfields and a image file.
Aparently, everything goes ok. The app is capable to correctly send data to server, the serve is capable of receiving the data and process it adequately (including the image file) and answer it with a status 200. The client even receives this status 200. However, the callback function called in the sencha touch app is the failure one, not success.
This is the response header that the client receives:
HTTP/1.0 200 OK
Date: Thu, 08 May 2014 20:59:29 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Cookie
X-Frame-Options: SAMEORIGIN
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
I'm doing the POST using this:
values = form.getValues();
var request = {
url: 'http://127.0.0.1:8000/profiles/create/',
method: 'POST',
success: function(conn, response, options, eOpts) {
Ext.Msg.alert('Success!', 'We are happy!.');
},
failure: function(conn, response, options, eOpts) {
Ext.Msg.alert('Error: status code ', response.status);
},
disableCaching: true,
xhr2: true,
progress: progressIndicator
}
form.submit(request)
How to known what i'm doing wrong?
From sencha docs about form.submit
success : Function
The callback that will be invoked after a successful response. A
response is successful if a response is received from the server and
is a JSON object where the success property is set to true,
{"success": true}.
So your response should be a valid json and contain success:true like this:
{success: true, data: 'mydata'}
I created an API /user/auth where I can send using POST a Json object like:
var user = {"username":"alex", "password":"m"}
$http(
{
method: 'POST',
url: '/api/v1/user/auth',
data: user,
}
).
success(function(data, status, headers, config) {
console.log(data)
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
The response from Django is the following:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type,*
Access-Control-Allow-Methods:POST,GET,OPTIONS,PUT,DELETE
Access-Control-Allow-Origin:*
Content-Language:fr
Content-Type:application/json
Date:Fri, 30 Aug 2013 15:22:01 GMT
Server:WSGIServer/0.1 Python/2.7.5
Set-Cookie:sessionid=w63m0aoo8m3vfmvv0vk5d6w1708ftsrk; Path=/
Vary:Accept, Accept-Language, Cookie
So Django returns a good cookie but I don't know why, Chrome doesn't set this cookie in Resource.
The request is sent from 127.0.0.1:8000 to 127.0.0.1:8080; I use this middleware to handle CROS requests and I also set:
SESSION_COOKIE_HTTPONLY = False
The problematic line is:
Access-Control-Allow-Origin: *
The credential request doesn't work with a wildcard allow origin. You have to specifically set the name, like :
Access-Control-Allow-Origin: http://127.0.0.1:8080
You can find more information here:
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#Requests_with_credentials
Ok thanks to Bqm link to mozilla I finally found why the cookie was not set.
Indeed you need to set in the header you sent:
Access-Control-Allow-Credentials: true
In Angular this is done with this method:
$http(
{
method: 'POST',
url: '/api/v1/user/auth',
data: user,
withCredentials: true
}
)
Once your backend will answer with a setCookie, the browser will then be able to set the cookie in your browser.
I am trying to send an object as JSON to my webservice in Flask that is expecting JSON in the request data.
I have tested the service manually by sending JSON data and it works fine. However, when I try to make a http POST request through angular controller, the web server sends me a message saying it did not receive JSON.
When I inspect the request headers in Chrome it appears that data is not being sent in JSON but regular key/value pairs even through the Content Type is set to application/json
Request Method:POST
Status Code:200 OK
Request Headersview source
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:49
Content-Type:application/json;charset=UTF-8
DNT:1
Host:localhost:5000
Origin:http://localhost:5000
Referer:http://localhost:5000/
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36
X-Requested-With:XMLHttpRequest
Request Payload
application=AirFare&d1=10-APR-2013&d2=14-APR-2013
If you seen the last line below Request Payload, you can see the data is not in JSON format.
This is the HTTP POST call in my angular controller:
$http({
url: '/user_to_itsr',
method: "POST",
data: {application:app, from:d1, to:d2},
headers: {'Content-Type': 'application/json'}
}).success(function (data, status, headers, config) {
$scope.users = data.users; // assign $scope.persons here as promise is resolved here
}).error(function (data, status, headers, config) {
$scope.status = status + ' ' + headers;
});
};
I am sending the data as an object {} but I have tried to send it after serializing by JSON.stringify however, nothing I do seems to send JSON to the server.
Really appreciate if somebody can help out.
If you are serializing your data object, it will not be a proper json object. Take what you have, and just wrap the data object in a JSON.stringify().
$http({
url: '/user_to_itsr',
method: "POST",
data: JSON.stringify({application:app, from:d1, to:d2}),
headers: {'Content-Type': 'application/json'}
}).success(function (data, status, headers, config) {
$scope.users = data.users; // assign $scope.persons here as promise is resolved here
}).error(function (data, status, headers, config) {
$scope.status = status + ' ' + headers;
});
I have tried your example and it works just fine:
var app = 'AirFare';
var d1 = new Date();
var d2 = new Date();
$http({
url: '/api/test',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: {application: app, from: d1, to: d2}
});
Output:
Content-Length:91
Content-Type:application/json
Host:localhost:1234
Origin:http://localhost:1234
Referer:http://localhost:1234/index.html
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36
X-Requested-With:XMLHttpRequest
Request Payload
{"application":"AirFare","from":"2013-10-10T11:47:50.681Z","to":"2013-10-10T11:47:50.681Z"}
Are you using the latest version of AngularJS?
You can use FormData API https://developer.mozilla.org/en-US/docs/Web/API/FormData
var data = new FormData;
data.append('from', from);
data.append('to', to);
$http({
url: '/path',
method: 'POST',
data: data,
transformRequest: false,
headers: { 'Content-Type': undefined }
})
This solution from http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
you can use your method by this way
var app = 'AirFare';
var d1 = new Date();
var d2 = new Date();
$http({
url: '/api/apiControllerName/methodName',
method: 'POST',
params: {application:app, from:d1, to:d2},
headers: { 'Content-Type': 'application/json;charset=utf-8' },
//timeout: 1,
//cache: false,
//transformRequest: false,
//transformResponse: false
}).then(function (results) {
return results;
}).catch(function (e) {
});
try to use absolute url. if it not works, check if service's response has headers:
Access-Control-Allow-Origin and Access-Control-Allow-Headers
for example:
"Access-Control-Allow-Origin": "*"
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"
you can use the following to find the posted data.
data = json.loads(request.raw_post_data)
$http({
url: '/api/user',
method: "POST",
data: angular.toJson(yourData)
}).success(function (data, status, headers, config) {
$scope.users = data.users;
}).error(function (data, status, headers, config) {
$scope.status = status + ' ' + headers;
});
I'm trying to set cookies with Go's net/http package. I have:
package main
import "io"
import "net/http"
import "time"
func indexHandler(w http.ResponseWriter, req *http.Request) {
expire := time.Now().AddDate(0, 0, 1)
cookie := http.Cookie{"test", "tcookie", "/", "www.domain.com", expire, expire.Format(time.UnixDate), 86400, true, true, "test=tcookie", []string{"test=tcookie"}}
req.AddCookie(&cookie)
io.WriteString(w, "Hello world!")
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":80", nil)
}
I tried googling 'Golang' with 'cookies', but didn't get any good results. If anyone can point me in the right direction it would be greatly appreciated.
I am not a Go expert, but I think you are setting the cookie on the request, aren't you? You might want to set it on the response. There is a setCookie function in net/http. This might help:
http://golang.org/pkg/net/http/#SetCookie
func SetCookie(w ResponseWriter, cookie *Cookie)
//ShowAllTasksFunc is used to handle the "/" URL which is the default ons
func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request){
if r.Method == "GET" {
context := db.GetTasks("pending") //true when you want non deleted notes
if message != "" {
context.Message = message
}
context.CSRFToken = "abcd"
message = ""
expiration := time.Now().Add(365 * 24 * time.Hour)
cookie := http.Cookie{Name: "csrftoken",Value:"abcd",Expires:expiration}
http.SetCookie(w, &cookie)
homeTemplate.Execute(w, context)
} else {
message = "Method not allowed"
http.Redirect(w, r, "/", http.StatusFound)
}
}
There is a basic difference between Requests and ResponseWriter, a Request is what a browser will send like
Host: 127.0.0.1:8081
User-Agent: ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://127.0.0.1:8081/
Cookie: csrftoken=abcd
Connection: keep-alive
and a response is what the handler will send, something like :
Content-Type: text/html; charset=utf-8
Date: Tue, 12 Jan 2016 16:43:53 GMT
Set-Cookie: csrftoken=abcd; Expires=Wed, 11 Jan 2017 16:43:53 GMT
Transfer-Encoding: chunked
<html>...</html>
When the browser will make a request, it'll include the cookie for that domain, since cookies are stored domain wise and can't be accessed from cross domains, if you set a cookie as HTTP only then it can only be accessed from the website which set it via HTTP and not via JS.
So when getting information from cookies you can do that from the r.Cookie method, like this
cookie, _ := r.Cookie("csrftoken")
if formToken == cookie.Value {
https://github.com/thewhitetulip/Tasks/blob/master/views/addViews.go#L72-L75
But when you are going to set a cookie, you have to do it in the response writer method, the request is a read only object which we respond to, think of it as a text message you get from someone, that is a request, you can only get it, what you type is a response, so you can type in a cookie at
for more details: https://thewhitetulip.gitbooks.io/webapp-with-golang-anti-textbook/content/content/2.4workingwithform.html
This Below code helps u
cookie1 := &http.Cookie{Name: "sample", Value: "sample", HttpOnly: false}
http.SetCookie(w, cookie1)
Below shows how we use cookie in our product:
func handleFoo(w http.ResponseWriter, r *http.Request) {
// cookie will get expired after 1 year
expires := time.Now().AddDate(1, 0, 0)
ck := http.Cookie{
Name: "JSESSION_ID",
Domain: "foo.com",
Path: "/",
Expires: expires,
}
// value of cookie
ck.Value = "value of this awesome cookie"
// write the cookie to response
http.SetCookie(w, &ck)
// ...
}
It was not working for me in Safari until I added the Path and MaxAge. Both secure and regular cookies worked for me
Sharing so that it helps someone who is stuck like me for more than 2 days :)
expire := time.Now().Add(20 * time.Minute) // Expires in 20 minutes
cookie := http.Cookie{Name: "username", Value: "nonsecureuser", Path: "/", Expires: expire, MaxAge: 86400}
http.SetCookie(w, &cookie)
cookie = http.Cookie{Name: "secureusername", Value: "secureuser", Path: "/", Expires: expire, MaxAge: 86400, HttpOnly: true, Secure: true}
http.SetCookie(w, &cookie)
First, you need to create Cookie and then using http package's SetCookie() function you can set the cookie.
expire := time.Now().Add(10 * time.Minute)
cookie := http.Cookie{Name: "User", Value: "John", Path: "/", Expires: expire, MaxAge: 90000}
http.SetCookie(w, &cookie)
You can use gorilla package for handling cookies or i would say secure cookies: http://www.gorillatoolkit.org/pkg/securecookie