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
Related
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
I'm calling my Flask view function (API) via Javascript (FETCH).
I'm having success with the GET method, but when using the PATCH method I'm receiving a 400 error code
(Failed to load resource: the server responded with a status of 400 (BAD REQUEST)).
I reviewed the url, seems ok and the ID is being passed as an integer, so no clue on why it's giving this error message.
This function (load_follow_link) is fetching via GET and updating my "Follow" to "Unfollow" tag (no errors here):
function load_follow_link(id) {
apply_csrf_token();
fetch(`/follow_unfollow/${id}`)
.then(response => response.json())
.then(data => {
if (data.is_following)
document.getElementById('follow-unfollow-btn').innerHTML = 'Unfollow';
else
document.getElementById('follow-unfollow-btn').innerHTML = 'Follow';
document.getElementById('followers-count').innerHTML = data.followers_count;
document.getElementById('following-count').innerHTML = data.following_count;
});
}
This is the PATCH function (follow_unfollow) triggering the error message. It is supposed to call the view function and update the DB:
function follow_unfollow(id) {
apply_csrf_token();
fetch(`/follow_unfollow/${id}`, {
method: 'PATCH'
})
.then(() => {
load_follow_link(id);
});
}
view function (doesn't get executed when request method is PATCH)
#app.route('/follow_unfollow/<int:tutor_id>', methods=['GET','PATCH'])
#login_required
def follow_unfollow(tutor_id):
""" GET: returns if user is following the tutor, followers and following total
PUT: follow or unfollow
"""
user = Users.query.get(current_user.id)
try:
tutor = Tutors.query.get(tutor_id)
except NoResultFound:
return JsonResponse({"error": "Tutor not registered"}, status=401)
following_count = user.following_total()
followers_count = tutor.followers_total()
is_following = user.is_following(tutor)
if request.method == 'GET':
return jsonify(is_following=is_following, followers_count=followers_count,
following_count=following_count)
elif request.method == 'PATCH':
if is_following:
user.unfollow(tutor)
else:
user.follow(tutor)
db.session.commit()
return success.return_response(message='Successfully Completed', status=204)
else:
return jsonify(error="GET or PUT request required", status=400)
I appreciate the help
Could be you are not returning a valid response. Where is success defined in success.return_response?
Why not just return jsonify(...)?
I have developed http server in python3 with http.server.
I have encountered problem that the does not done sending post response, so browser keep loading too much time.
Which side (server or browser) causes this problem?
and How do I fix it?
Python3.6.3
macOS 10.13
import http.server
class MyHandler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_POST(self):
# Now this method just print path and content-type.
print("POSTED")
content_type = self.headers["Content-Type"]
print(content_type)
print(self.path)
if "multipart/form-data" in content_type:
raw_data = self.rfile.read()
self.send_response(200, self.responses[200][0])
self.send_header("access-control-allow-origin", "*")
self.send_header("connection", "close")
self.end_headers()
# WIP: do something...
def do_GET(self):
if self.path[0] == "/":
self.path = self.path[1:]
try:
with open(self.path, "rb") as f:
file_data = f.read()
except FileNotFoundError:
self.send_response(404, self.responses[404][0])
self.end_headers()
return
content_length = len(file_data)
self.send_response(200, self.responses[200][0])
self.send_header("content-length", content_length)
self.end_headers()
self.wfile.write(file_data)
def parse_post():
# WIP
pass
httpd = http.server.HTTPServer(("", 6788), MyHandler)
print("Address:", "", "Port:", 6788)
httpd.serve_forever()
browser js:
let formdata = new FormData();
formdata.append("Hello", "World");
fetch("http://localhost:6788/nk", {
method: "post",
mode: "cors",
body: formdata,
})
I am trying to call the function ssl_verify from my html template . But it gives 404 error. Can anyone help me with this? Where i am wrong ?
#views.py
def ssl_verify( request , dns, port ):
if request.is_ajax():
result = dns + port
return result
#urls.py
url(r'ssl_verify/(\d+)/(\d+)/$', views.ssl_verify,name='ssl_verify'),
#script in html
function verify()
{
dns = document.getElementById("dns1").value;
port = document.getElementById("port1").value;
$.post('ssl_verify/'+dns+'/'+port+'/', function (data) {
alert (data) ;
});
}
Your urls.py must contain (replace the regexp line)
url(r'^ssl_verify/(?P<dns>[^/]+)/(?P<port>[^/]+)/$','views.check_ssl'),
or if you import:
from views import check_ssl
....
url(r'^ssl_verify/(?P<dns>[^/]+)/(?P<port>[^/]+)/$','check_ssl'),
and your views:
from django.http import HttpResponse
def check_ssl( request , dns, port ):
if request.is_ajax():
message = str(dns)+str(port)
return HttpResponse(message)
else:
return HttpResponse('/faulthandler') # declare in urls if needed
I'm trying to give users on my website "points" or "credits" for tweeting about out the brand name.
I have the fancy twitter widget on the appropriate view...
<p>Tweet
<div id="credited"></div>
<script>window.twttr = (function (d, s, id) {
var t, js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src= "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
return window.twttr || (t = { _e: [], ready: function (f) { t._e.push(f) } });
}(document, "script", "twitter-wjs"));
</script>
I have the JS all written up and pretty....
function creditTweet() {
$.post(
"/credit_tweet",
{},
function(result) {
var text;
if (result.status === "noop") {
text = "Thanks for sharing already!";
} else if (result.status === "ok") {
text = "5 Kredit Added";
}
$("#credited").html(text);
}
);
}
$(function() {
twttr.ready(function (twttr) {
window.twttr.events.bind('tweet', creditTweet);
});
});
Now the problem is either in the controller OR in the routes (where I'm posting). I think the routes are fine because the POST is almost working, because this is the description of the error on wikipedia - "422 Unprocessable Entity (WebDAV; RFC 4918)
The request was well-formed but was unable to be followed due to semantic errors."
So, do you guys see anything wrong with my ruby code in the controller?
class SocialKreditController < ApplicationController
TWEET_CREDIT_AMOUNT = 5
def credit_tweet
if !signed_in?
render json: { status: :error }
elsif current_user.tweet_credited
Rails.logger.info "Not crediting #{ current_user.id }"
render json: { status: :noop }
else
Rails.logger.info "Crediting #{ current_user.id }"
current_user.update_attributes tweet_credited: true
current_user.add_points TWEET_CREDIT_AMOUNT
render json: { status: :ok }
end
end
end
And in my routes.rb, it's pretty straight forward, so I doubt there's anything wrong here...
get 'social_kredit/credit_tweet'
post '/credit_tweet' => 'social_kredit#credit_tweet'
Where oh where is this error? I clearly don't know smack about HTTP requests.
I got it working!
I added a...
skip_before_action :verify_authenticity_token
to the controller.
The issue was found when checking out the logs and seeing that the CSRF token could not be verified.
ihaztehcodez(who was last active in 2016 so it won't help nudging him to post an answer) mentions that the skip_before_action :verify_authenticity_token technique is not so secure 'cos you lose forgery protection.
they mention that the best/secure/'better practise', solutions are mentioned here WARNING: Can't verify CSRF token authenticity rails
e.g.
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
or
$.ajax({ url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
or
putting this within an ajax request
headers: {
'X-Transaction': 'POST Example',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
Same problem I faced.
It sorts out after adding
skip_before_action :verify_authenticity_token
at the top of your controller where your JS is calling or sending data.
class UserController < ApplicationController
skip_before_action :verify_authenticity_token
def create
end
end
as shown in code snippet.
I had this challenge when working on a Rails 6 API-only application.
I followed the answer here - Rails: How to implement protect_from_forgery in Rails API mode to implement protect_from_forgery in Rails API mode, but I was still having the Can't verify CSRF token authenticity error followed by the 422 Unprocessable Entity error when I send post requests from Postman:
Started POST "/api/v1/programs" for ::1 at 2021-02-23 18:42:49 +0100
Processing by Api::V1::ProgramsController#create as JSON
Parameters: {"program"=>{"name"=>"Undergraduate", "code"=>"UD", "affiliate_status"=>false, "motto"=>"Our motto is ...", "description"=>"This is a new program", "school_id"=>1}}
Can't verify CSRF token authenticity.
TRANSACTION (0.3ms) BEGIN
↳ app/controllers/api/v1/programs_controller.rb:27:in `create'
Baserecord::School Load (0.3ms) SELECT "schools".* FROM "schools" WHERE "schools"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/api/v1/programs_controller.rb:27:in `create'
TRANSACTION (0.4ms) ROLLBACK
↳ app/controllers/api/v1/programs_controller.rb:27:in `create'
Completed 422 Unprocessable Entity in 30ms (Views: 0.8ms | ActiveRecord: 6.9ms | Allocations: 13172)
Here's I solved it:
The issue was caused by a validation error from my models. A validation I added to the model for the controller I was calling was failing. I had to check the Body of the response returned by Postman after making the request to find the error:
{
"affiliate_status": [
"can't be blank"
]
}
Once I fixed the error following this answer - Rails: Validation fails for ActiveRecord in setting a random Boolean Attribute, everything worked fine afterward.
That's all.
I hope this helps
a 422 can happen when updating a model in a POST request:
e.g. #user.update!(email: nil) in users#update will make a 422 if there is a validation on email like validates :email, presence: true
If you're including Rails meta data in the HTML header with <%= csrf_meta_tags %> it'll generate the following.
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="ihwlaOLL232ipKmWYaqbSZacpJegQqooJ+Cj9fLF2e02NTQw7P/MfQyRuzruCax2xYWtEHWsb/uqiiZP6NWH+Q==" />
You can pull the CRSF token from the meta data and pass it into your async request.
Using the native js fetch method you can pass it in as a x-csrf-token header.
This is a trimmed onSave handler for a React component that enhances a standard Rails form.
onSaveHandler = (event) => {
const data = "Foo Bar";
const metaCsrf = document.querySelector("meta[name='csrf-token']");
const csrfToken = metaCsrf.getAttribute('content');
fetch(`/posts/${this.props.post_id}`, {
method: "PUT",
body: JSON.stringify({
content: data
}),
headers: {
'x-csrf-token': csrfToken,
'content-type': 'application/json',
'accept': 'application/json'
},
}).then(res => {
console.log("Request complete! response:", res);
});
}
Forgery protection is a good idea. This way we stay secure and don't mess with our Rails configuration.
Using gem 'rails', '~> 5.0.5' & "react": "^16.8.6",