I've been trying for hours to send a POST request to an endpoint in my Django application from my separated VueJS frontend using Axios. The problem with my code is that whatever i try i will always get Forbidden (CSRF cookie not set.), and i can't use #crsf_exempt.
I tried every possible solution i found, from changing headers names in my Axios request to setting CSRF_COOKIE_SECURE to False, nothing seems to solve this problem.
Here is my request:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
console.log(cookieValue)
return cookieValue;
}
function req(){
this.csrf_token = getCookie('csrftoken')
axios({
method: 'post',
url: 'http://127.0.0.1:8000/backend/testreq/',
data: {
//Some data here
},
headers: {'Content-Type': 'application/json', 'X-CSRFToken': this.csrf_ftoken }
}).then(function (response) {
console.log(response)
}).catch(function (error) {
console.log(error)
});
},
The token is being sent but the outcome is always the same. The Django app is using Django-Rest-Framework too, i don't know if that's the problem.
Here is some of my settings.py (for development):
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_HEADERS = list(default_headers) + [
'xsrfheadername',
'xsrfcookiename',
'content-type',
'csrftoken',
'x-csrftoken',
'X-CSRFTOKEN',
]
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
CSRF_TRUSTED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
SESSION_COOKIE_SAMESITE = None
CSRF_COOKIE_SAMESITE = None
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
SESSION_COOKIE_SECURE = False
I don't know what else can i try to solve this problem, any advice is appreciated
The Default Authentication Scheme in Django Rest Framework is
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
Session Authentication requires a CSRF Token when you make POST requests unless exempted using #csrf_exempt
CSRF_COOKIE_SECURE in Django only ensures that CSRF Tokens are sent via HTTPS
To Fix Your Issue, you can change DEFAULT_AUTHENTICATION_CLASSES from the default to
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
]
}
Once you switch to TokenAuthentication you'll have to exchange the user's credentials for an Auth token and use that Token For Subsequent requests
Django Rest Framework's guide on Token Authentication
You can also take a look at this SO answer here to use #csrf_exempt on class based views
I suppose it's a problem caused by 'cross-domain', you cannot set cookie or store it in browser generated by backend localhost:8000 through frontend localhost:8080. If you want to store or modify cookie you can only access localhost:8000.
You can use Nginx as reverse proxy to solve the problem, Here is the video for details
https://youtube.com/watch?v=VeDms9GPaLw
Related
I have a download route in my MeteorJs app which i want to restrict access to. The route code is as follows
Router.route("/download-data", function() {
var data = Meteor.users.find({ "profile.user_type": "employee" }).fetch();
var fields = [...fields];
var title = "Employee - Users";
var file = Excel.export(title, fields, data);
var headers = {
"Content-type": "application/vnd.openxmlformats",
"Content-Disposition": "attachment; filename=" + title + ".xlsx"
};
this.response.writeHead(200, headers);
this.response.end(file, "binary");
},
{ where: "server" }
);
The route automatically downloads a file. This is currently working but I want to restrict access to the route. I only want admins to be able to download it.
I have created an onBeforeAction Hook as below
Router.onBeforeAction(
function() {
//using alanning:roles
if(Roles.userIsInRole(this.userId, "admin"){
console.log('message') //testing
}
},
{
only: ["downloadData"]
}
);
and renamed my route as below
//code above
this.response.writeHead(200, headers);
this.response.end(file, "binary");
},
{ where: "server", name: "downloadData" }
);
The onBeforeAcion hook does not take any effect
Also I noticed neither this.userId nor Meteor.userId works on the route
For the server side hook, I am pretty sure you need the onBeforeAction to have the { where: "server" } part as you do for your route.
Also, I don't think iron:router ever implemented server side user auth on their routing. You may want to check into a package around server routing with larger features such as mhagmajer:server-router that has access to authenticated routes.
https://github.com/mhagmajer/server-router
I'm trying to post a tweet but for any reason it doesn't work as expected.
I suspect that the issue could be related to the signature string, but following what twitter says according to signing requests looks ok.
Here is my code:
function postTweet(user_id, AccessToken, AccessTokenSecret) {
var base_url = 'https://api.twitter.com/1.1/statuses/update.json',
oauth_nonce = randomString(),
oauth_signature,
oauth_timestamp = Math.floor(new Date().getTime() / 1000),
reqArray,
req,
signature_base_string,
signing_key;
reqArray = [
"include_entities=true",
'oauth_consumer_key="' + CONFIG.TWITTER_CONSUMER_KEY + '"',
'oauth_nonce="' + oauth_nonce + '"',
'oauth_signature_method="HMAC-SHA1"',
'oauth_timestamp="' + oauth_timestamp + '"',
'oauth_token="' + AccessToken + '"',
'oauth_version="1.0"',
'status=' + encodeURIComponent("hello world")
];
req = reqArray.sort().join('&');
signature_base_string = "POST&" + encodeURIComponent(base_url) + "&" + encodeURIComponent(req);
signing_key = CONFIG.TWITTER_CONSUMER_KEY_SECRET + '&' + AccessTokenSecret;
oauth_signature = encodeURIComponent(CryptoJS.HmacSHA1(signature_base_string, signing_key).toString(CryptoJS.enc.Base64));
return $http.post('https://api.twitter.com/1.1/statuses/update.json', {
status: 'hello world'
}).then(function (response) {
return response;
}).catch(function (error) {
console.log(error);
});
}
As a response, I get that:
UPDATE
considering that in my project I already have $cordovaOauthUtility I started using it this way:
function postTweet(accessToken, accessTokenSecret) {
var params, signature;
params = {
include_entities: true,
oauth_consumer_key: CONFIG.TWITTER_CONSUMER_KEY,
oauth_nonce: $cordovaOauthUtility.createNonce(10),
oauth_signature_method: "HMAC-SHA1",
oauth_token: accessToken,
oauth_timestamp: Math.round((new Date()).getTime() / 1000.0),
oauth_version: "1.0"
};
signature = $cordovaOauthUtility.createSignature('POST', 'https://api.twitter.com/1.1/statuses/update.json', params, { status: "hello" }, CONFIG.TWITTER_CONSUMER_KEY_SECRET, accessTokenSecret);
return $http.post('https://api.twitter.com/1.1/statuses/update.json', {
status: "hello"
}, {
headers: {
Authorization: signature.authorization_header
}
})
.then(function (response) {
return response;
}).catch(function (error) {
console.log(error);
});
}
UPDATE 2
After trying all the posibilities, the problem persist. Here I paste a plnkr where I have my code.
You are using crypto's HmacSHA256 but sending HMAC-SHA1 as the oauth_signature_method parameter which is the twitter one.
You should probably change your code to
oauth_signature = CryptoJS.HmacSHA1(signature_base_string, signing_key).toString(CryptoJS.enc.Base64);
If you look at your authorization header, you can also notice that something is wrong with it. Indeed, you can see that the oauth_nonce and the oauth_version are prefixed by a & sign, which shouldn't be the case and most likely mean to the api you are not specifying them. It probably comes from the fact you are using the same reqArray to construct both the signature and the header, or your code is not updated.
You also probably don't want to change the global headers sent from your app, in case another request is sent to another api at the same time. Rather, you should send this authorization header only for this specific xhr.
return $http.post('https://api.twitter.com/1.1/statuses/update.json', {
status: 'hello world',
}, {
headers: {
Authorization: auth,
},
})
Well, you're clearly adding oauth_token in your request array but it didn't show up in the screenshot? Is the AccessToken in the params undefined?
EDIT
According to the documentation, we must append double quotes to the headers. Try this?
reqArray = [
"include_entities=true",
'oauth_consumer_key="'+CONFIG.TWITTER_CONSUMER_KEY+'"',
'oauth_nonce="'+oauth_nonce+'"',
'oauth_signature_method="HMAC-SHA1"',
'oauth_timestamp="'+oauth_timestamp+'"',
'oauth_token="'+AccessToken+'"',
'oauth_version="1.0"',
'status='+encodeURIComponent("hello world")
];
Yikes.
I've downloaded your plnkr bundle and added a read only application key set. I only had to set up and make one change to get a {"request":"\/1.1\/statuses\/update.json","error":"Read-only application cannot POST."} response. Initially I was receiving {"errors":[{"code":32,"message":"Could not authenticate you."}]}.
Remove status: "hello" from between the curly brackets { } where you create your signature.
signature = $cordovaOauthUtility.createSignature('POST', 'https://api.twitter.com/1.1/statuses/update.json', params, { }, twitter.consumer_secret, twitter.access_token_secret);
My request headers become the following:
:authority:api.twitter.com
:method:POST
:path:/1.1/statuses/update.json
:scheme:https
accept:application/json, text/plain, */*
accept-encoding:gzip, deflate, br
accept-language:en-US,en;q=0.8
authorization:OAuth oauth_consumer_key="x",oauth_nonce="QFMmqiasFs",oauth_signature_method="HMAC-SHA1",oauth_token="y",oauth_timestamp="1496340853",oauth_version="1.0",oauth_signature="7Ts91LKcP%2FrYsLcF5WtryCvZQFU%3D"
content-length:18
content-type:application/json;charset=UTF-8
origin:http://localhost
referer:http://localhost/twits/
user-agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36
Googling eventually led me to a tutorial:
Displaying the Twitter Feed within Your Ionic App. I noted his general createTwitterSignature function does not take parameters and tweaked your code similarly.
function createTwitterSignature(method, url) {
var token = angular.fromJson(getStoredToken());
var oauthObject = {
oauth_consumer_key: clientId,
oauth_nonce: $cordovaOauthUtility.createNonce(10),
oauth_signature_method: "HMAC-SHA1",
oauth_token: token.oauth_token,
oauth_timestamp: Math.round((new Date()).getTime() / 1000.0),
oauth_version: "1.0"
};
var signatureObj = $cordovaOauthUtility.createSignature(method, url, oauthObject, {}, clientSecret, token.oauth_token_secret);
$http.defaults.headers.common.Authorization = signatureObj.authorization_header;
}
I've read conflicting things about why there should/shouldn't be other parameters there, but I believe the signature is just supposed to be the basis of access and doesn't hash in every operation you want to perform - see Understanding Request Signing For Oauth 1.0a Providers.
I am trying to login to a system. In angular 1, there was ways to set
withCredentials:true
But I could not find a working solution in angular2
export class LoginComponent {
constructor(public _router: Router, public http: Http, ) {
}
onSubmit(event,username,password) {
this.creds = {'Email': 'harikrishna#gmail.com','Password': '01010','RememberMe': true}
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.http.post('http://xyz/api/Users/Login', {}, this.creds)
.subscribe(res => {
console.log(res.json().results);
});
}
}
In Angular > 2.0.0 (and actually from RC2 on), just
http.get('http://my.domain.com/request', { withCredentials: true })
AFAIK, right now (beta.1) the option is not available.
You have to work around it with something like this:
let _build = http._backend._browserXHR.build;
http._backend._browserXHR.build = () => {
let _xhr = _build();
_xhr.withCredentials = true;
return _xhr;
};
This issue has been noted by the angular2 team.
You can find some other workarounds (one especially written as an #Injectable) following the issue link.
If anyone is using plain JS, based on cexbrayat's answer:
app.Service = ng.core
.Class({
constructor: [ng.http.Http, function(Http) {
this.http = Http;
var _build = this.http._backend._browserXHR.build;
this.http._backend._browserXHR.build = function() {
var _xhr = _build();
_xhr.withCredentials = true;
return _xhr;
};
}],
I think you don't use the post metrhod the right way. You could try something like that:
onSubmit(event,username,password) {
this.creds = {
'Email': 'harikrishna#gmail.com',
'Password': '01010','RememberMe': true
}
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.http.post('http://xyz/api/Users/Login',
JSON.stringify(this.creds),
{ headers: headers });
}
You invert parameters. The second parameter corresponds to the content to send into the POST and should be defined as string. Objects aren't supported yet at this level. See this issue: https://github.com/angular/angular/issues/6538.
If you want to set specific headers, you need to add the Headers object within the third parameter of the post method.
Otherwise, I think the withCredentials property is related to CORS if you want to send cookies within cross domain requests. You can have a look at this link for more details:
http://restlet.com/blog/2015/12/15/understanding-and-using-cors/
http://restlet.com/blog/2016/09/27/how-to-fix-cors-problems/
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
Hope it helps you,
Thierry
getHeaders(): RequestOptions {
let optionsArgs: RequestOptionsArgs = { withCredentials: true }
let options = new RequestOptions(optionsArgs)
return options;
}
getAPIData(apiName): Observable<any> {`enter code here`
console.log(Constants.API_URL + apiName);
let headers = this.getHeaders();
return this.http
.get(Constants.API_URL + apiName, headers)
.map((res: Response) => res.json())
}
Enabled cors in the webapi
Same code works fine in the chrome(normal),Internet explorer
But it is asking windows login prompt in the incognito chrome,firefox,edge.
Share the suggestions how to fix the issue
for CORS issue with withCredentials : yes, I send the auth token as parameter
req = req.clone({
//withCredentials: true,
setHeaders: { token: _token },
setParams: {
token: _token,
}
});
I've got my authorization system working nicely with Ember Data. All my ember-data calls are signed with the correct tokens by using adapater.ajax() instead of $.ajax. However, I've got a case where I am using a 3rd party upload library which uses its own XHR request (jquery.fileapi). This library exposes a "headers" property for the requests it makes, but I'm not sure what the best way is to get the headers out of my adapter and pass it the file upload component I'm building.
ApplicationAdapter:
export default DS.ActiveModelAdapter.extend({
namespace: 'api/v1',
headers: function() {
var authToken = this.get('session.authToken') || 'None';
return {
'Authorization': Ember.String.fmt('Bearer %#', authToken)
};
}.property('session.authToken')
});
ImageUploadComponent:
didInsertElement: function() {
this.$('.js-uploader').fileapi({
url: '/api/v1/users/avatar',
accept: 'image/*',
headers: {'?????????????'}
});
}
I'd rather not define a global in "headers" when the 'session.authToken' changes.
Here's what I'm doing for now. Would love other solutions.
DS.Store.reopen({
apiPathFor: function() {
var url = arguments.length ? Array.prototype.slice.call(arguments).join('/') : ''
, adapter = this.adapterFor('application');
return [adapter.urlPrefix(), url].join('/');
}
});
export default Ember.Component.extend({
endpoint: null,
store: Ember.computed.readOnly('targetObject.store'),
didInsertElement: function() {
var store = this.get('store')
, adapter = store.adapterFor('application')
, headers = adapter.get('headers')
, url = store.apiPathFor(this.get('endpoint'));
var args = {
url: url,
headers: headers,
accept: 'image/*'
};
this.$('.js-fileapi').fileapi(args);
},
});
I'm developing a RESTful application with ExtJS (client) and Flask (server): client and server are linked by a protocol.
The problem comes when I try to do an AJAX request to the server, like this:
Ext.Ajax.request ({
url: 'http://localhost:5000/user/update/' + userId ,
method: 'POST' ,
xmlData: xmlUser ,
disableCaching: false ,
headers: {
'Content-Type': 'application/xml'
} ,
success: function (res) {
// something here
} ,
failure: function (res) {
// something here
}
});
With the above request, the client is trying to update the user information.
Unfortunately, this is a cross-domain request (details).
The server handles that request as follows:
#app.route ("/user/update/<user_id>", methods=['GET', 'POST'])
def user_update (user_id):
return user_id
What I see on the browser console is an OPTIONS request instead of POST.
Then, I tried to start the Flask application on the 80 port but it's not possible, obviously:
app.run (host="127.0.0.1", port=80)
In conclusion, I don't understand how the client can interact with the server if it cannot do any AJAX request.
How can I get around this problem?
Here's an excellent decorator for CORS with Flask.
http://flask.pocoo.org/snippets/56/
Here's the code for posterity if the link goes dead:
from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper
def crossdomain(origin=None, methods=None, headers=None,
max_age=21600, attach_to_all=True,
automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, basestring):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, basestring):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
You get around the problem by using CORS
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
The module Flask-CORS makes it extremely simple to perform cross-domain requests:
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
See also: https://pypi.python.org/pypi/Flask-Cors