I am struggling with 403/CSRF issues when trying to use Angular 2 to POST to my Django server.
Both my server code and my Angular code are running on the same 127.0.0.1 server.
When the Angular code is run the server returns a 403 4612 error
My Django View code looks like this (I am using the Django REST Framework):
rom django.utils import timezone
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.models import User
from .models import Item, Seen, Keyword, Flag
from django.utils.decorators import classonlymethod
from django.views.decorators.csrf import csrf_exempt
from rest_framework import viewsets
from items.serializers import ItemSerializer, UserSerializer
from rest_framework.authentication import SessionAuthentication
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all().order_by('-date_added')
serializer_class = ItemSerializer
authentication_classes = (CsrfExemptSessionAuthentication, )
#permission_classes = [IsAccountAdminOrReadOnly]
"""
Use the API call query params to determing what to return
API params can be:
?user=<users_id>&num=<num_of_items_to_return>&from=<user_id_of_items_to_show>
"""
def get_queryset(self):
this_user = self.request.query_params.get('user', None)
restrict_to_items_from_user_id = self.request.query_params.get('from', None)
quantity = self.request.query_params.get('num', 20)
if restrict_to_items_from_user_id is not None:
queryset = Item.objects.filter(owner=restrict_to_items_from_user_id, active=True).order_by('-date_added')[0:int(quantity)]
elif this_user is not None:
queryset = Item.objects.filter(active=True, credits_left__gt=0).exclude(pk__in=Seen.objects.filter(user_id=this_user).values_list('item_id', flat=True))[0:int(quantity)]
else:
queryset = Item.objects.filter(active=True, credits_left__gt=0)[0:int(quantity)]
return queryset
My Angular 2 code that does the POST looks like this:
import {Injectable, Inject} from 'angular2/core';
import {Http, Headers, HTTP_PROVIDERS} from 'angular2/http';
import {UserData} from './user-data';
import 'rxjs/add/operator/map';
#Injectable()
export class ConferenceData {
static get parameters(){
return [[Http], [UserData]];
}
constructor(http, user) {
// inject the Http provider and set to this instance
this.http = http;
this.user = user;
}
load() {
// Example of a PUT item
let body = JSON.stringify({ url: 'fred', item_type: 'P', owner_id: 2 });
let headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post('http://127.0.0.1:8000/api/items/', body, {
headers: headers
})
.subscribe(
data => {
alert(JSON.stringify(data));
},
err => this.logError(err.json().message),
() => console.log('Authentication Complete')
);
}
}
I find CSRF issues really difficult to get to grips with!
EDIT: Added CORS settings
My CORS settings look like this:
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'veeu.co',
'127.0.0.1'
)
CORS_ORIGIN_REGEX_WHITELIST = ()
CORS_URLS_REGEX = '^.*$'
CORS_ALLOW_METHODS = (
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'UPDATE',
'OPTIONS'
)
CORS_ALLOW_HEADERS = (
'x-requested-with',
'content-type',
'accept',
'origin',
'authorization',
'x-csrftoken'
)
CORS_EXPOSE_HEADERS = ()
CORS_ALLOW_CREDENTIALS = False
Rather than disabling the CSRF protection, you can add the token as a header to your ajax requests. See the docs, in particular the last section for AngularJS.
You might have to use csrf_ensure for the initial Django view, to ensure that Django sets the csrf cookie.
Related
I am working on the administrator part of a beginner's project I'm working on. I'm building in React.js with Pymongo/Flask connected to MongoDB Atlas for database storage. The page I'm working on allows the administrator to query the database to return all the users for a particular course they are taking or role they have (instructor or administrator). The returned data is mapped over to child components in React with a series of input fields using the defaultValue being populated by the props for the children (i.e. first name, last name, email, etc.). I'm saving new values to the child components' states and using JSON.stringify to make an axios.patch request. I'd like to be able to alter any user's information and submit it to the Mongo DB Atlas server, but am having some issues.
Here is what I think would be the necessary code from the front end:
saveChanges(id, data) {
var token = window.sessionStorage.getItem("token")
const updata = JSON.stringify(data)
axios.patch(`http://127.0.0.1:5000/update-instructor/${id}`, JSON.stringify({updata}), { headers: {"Authorization" : `Bearer ${token}`}})
.catch(error => {
console.log("There was an error with the patch request to instructor", error)
})
}
On the backend, this is the route that axios is calling:
#app.route('/update-instructor/<id>', methods=['GET', 'PATCH'])
def update_one_instructor(id):
id = ObjectId(id)
id_call = {"_id" : id}
updateObject = request.get_json(force=True)
instructors.find_one_and_update(id_call,
{ "$set" : { updateObject } },
return_document = ReturnDocument.AFTER)
The imports and setup of my flask/Pymongo:
import datetime
from distutils.log import error
import json
import pymongo
from bson.objectid import ObjectId
from bson import json_util
from flask_jwt_extended import create_access_token
from flask_jwt_extended import decode_token
from flask_jwt_extended import JWTManager
from flask_jwt_extended import jwt_required
from flask import Flask, jsonify, make_response, Response, request
from flask_cors import CORS, cross_origin
from pymongo import ReturnDocument
from werkzeug.security import generate_password_hash, check_password_hash
CONNECTION_URL = *connection url*
app = Flask(__name__)
app.config['CORS_HEADERS'] = 'Content-Type'
cors = CORS(app)
app.config['JWT_SECRET_KEY'] = *secret key*
jwt = JWTManager(app)
try:
client = pymongo.MongoClient(CONNECTION_URL, serverSelectionTimeoutMS = 10000)
except:
print("Error - cannot connect to database")
Database = client.get_database(*database name*)
instructors = Database.instructors
I'm getting several issues. On the front end in Chrome, I am getting:
Access to XMLHttpRequest at 'http://127.0.0.1:5000/update-instructor/*string of ObjectID*' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
as well as:
PATCH http://127.0.0.1:5000/update-instructor/*string of ObjectID* net::ERR_FAILED
On the backend I'm getting a 400 error:
127.0.0.1 - - [14/Mar/2022 17:17:43] "OPTIONS /update-instructor/*string of ObjectID* HTTP/1.1" 400 -
Might be unecessary information here; but I'm not sure what is relevant. Any ideas on how I can get this patch request to go through and update MongoDB Atlas and, subsequently, the state in my parent component?
I found the solution. It seems it was an error in my Pymongo/Flask setup.
#app.route('/update-user/<id>', methods=['GET', 'PATCH'])
def update_one_user(id):
id = ObjectId(id)
updateObject = request.get_json()
jsonify(updateObject)
result = users.find_one_and_update({"_id" : id},
{ "$set" : updateObject },
return_document = ReturnDocument.AFTER)
return "User Updated"
I also did some refactoring, so the route is slightly changed. Basically it seems that using fewer variables as well as well as removing the {} from updateObject did the trick. But I also refactored my front end code to
saveChanges(id, data) {
let config = {
headers: {
"Content-Type": "application/json",
'Access-Control-Allow-Origin': '*'
}
}
axios.patch(`http://127.0.0.1:5000/update-user/${id}`, JSON.stringify(data), config)
.catch(error => {
console.log("There was an error with the patch request to instructor", error)
})
}
It now includes some extra headers for CORS, but was was pointed out to me, it was the http 400 that was causing the CORS issue.
I have used the following code to make a Coinbase API request, but I keep getting the 401 authentication error.
My request seems to be providing the right header information and I do have an active API in Coinbase Pro.
Any idea what I am doing wrong?
import hashlib
import hmac
from datetime import datetime
import requests
from requests.auth import AuthBase
URL = 'https://api.exchange.coinbase.com'
request_path = '/accounts/accountid'
#coinbase pro
API_KEY = {API_KEY}
API_SECRET = {API_SECRET}
passphrase = {passphrase}
class Auth(AuthBase):
VERSION = b'2021-03-30'
def __init__(self, API_KEY, API_SECRET, passphrase):
self.API_KEY = API_KEY
self.API_SECRET = API_SECRET
self.passphrase = passphrase
def __call__(self, request):
timestamp = datetime.now().strftime('%s')
message = f"{timestamp}{request.method}{request.path_url}{request.body or ''}"
signature = hmac.new(self.API_SECRET.encode(),
message.encode('utf-8'),
digestmod=hashlib.sha256)
signature_hex = signature.hexdigest()
request.headers.update({
'CB-ACCESS-SIGN': signature_hex,
'CB-ACCESS-TIMESTAMP': timestamp.encode(),
'CB-ACCESS-KEY': self.API_KEY.encode(),
'CB-VERSION': self.VERSION,
'CB-ACCESS-PASSPHRASE': self.passphrase,
'Accept': 'application/json'
})
return request
auth = Auth(API_KEY, API_SECRET,passphrase)
response = requests.request("GET",f'{URL}{request_path}', auth=auth)
response
I have been trying to POST a request with data using fetch in React.js. How can I receive all the data sent in the body of the request options? I am attaching the serializer models for better understanding.
React Frontend(sending the post request with the data)
handleOnSubmit = (event)=>{
const requestOptions = {
method : "POST",
headers : {
Accept: "application/json",
"Content-Type":"application/json"},
body : JSON.stringify({
name:this.state.name,
est:this.state.est,
org:this.state.org
}),
};
fetch("api/add-company", requestOptions).then((response)=>{return response.json()}).then((data)=>{
console.log(data)
})
event.preventDefault();
}
models.py
class Company(models.Model):
name = models.CharField(max_length=30, blank= False, null=False, unique=True)
established = models.IntegerField()
origin = models.CharField(max_length=64, null=False, blank=False)
serializers.py
from rest_framework import serializers
from .models import Company,Brand
class AddCompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ['name', 'established', 'origin']
extra_kwargs = {'name':{'required':False},'established':{'required':False},'origin':{'required':False}}
If you want to get the data send as body of POST request then you can use request.POST.get() method. In your case it would be:
name = json.loads(request.POST.get('name'))
est = json.loads(request.POST.get('est'))
org = json.loads(request.POST.get('org'))
You need to save your serializer.
if serializer.is_valid():
response = serializer.save()
return Response(response.data, status=status.HTTP_201_CREATED)
I'm wrestling with cross origin headers while testing my app:
react side:
const url = "http://localhost:5000/blog/posts";
const headers = { headers: "Access-Control-Allow-Origin" };
axios.post(url, data, headers).then( ...
Flask backend __init__.py :
...
...
from flask_cors import CORS
def create_app(script_info=None):
app = Flask(__name__)
from project.api.blog import blog_blueprint
from project.api.auth import auth_blueprint
CORS(blog_blueprint, resources={'origin': ['http://localhost:3000']})
app.register_blueprint(blog_blueprint)
app.register_blueprint(auth_blueprint)
return app
the above gives me an exception in the catch block of the try-catch statement:
TypeError: name.toUpperCase is not a function
using Flask's defaults which means exposing the endpoint to any domain:
from project.api.blog import blog_blueprint
from project.api.auth import auth_blueprint
CORS(blog_blueprint)
gives me Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/blog/posts. (Reason: CORS header 'Access-Control-Allow-Origin' missing)
I've also tried to use a decorator from Flask-CORS:
from flask_cors import cross_origin
class BlogPosts(Resource):
#cross_origin()
def post(self):
parser.add_argument('category', type=str)
parser.add_argument('title', type=str)
parser.add_argument('post', type=str)
args = parser.parse_args()
new_post = Posts(title=args.title, category=args.category, post=args.post)
db.session.add(new_post)
db.session.commit()
return {'status': 'success', 'message': 'post added'}, 201
Any help is much appreciated.
Strangely, I fixed my problem by refactoring my code into function-based view:
CORS(blog_blueprint)
#blog_blueprint.route('/posts', methods=['GET', 'POST'])
#cross_origin()
def blog_posts():
if request.method == 'POST':
post_data = request.get_json()
category = post_data.get('category')
title = post_data.get('title')
post = post_data.get('post')
new_post = Posts(title=title, category=category, post=post)
db.session.add(new_post)
db.session.commit()
return {'status': 'success', 'message': 'post added'}, 201
return {'status': 'success', 'message': [post.to_json() for post in Posts.query.filter_by(category=category)]}, 200
I'm not very happy with this because Flask's CORS library should work same regardless if I'm using class-based or function-based view for handling APIs.
I am pretty new to Django, but I want to learn how to implement a DRF Token Authentication with Angularjs. Some of the tutorials I have found haven't been too helpful in showing how to set it up, along with their source code etc...
Also, for production purposes, is it more practical to use a third party package? Or set up my own (it's for a personal project, so time contribution is not an issue).
My Buggy Code for Token Auth: Github
In settings.py
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
In signals.py
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
In views.py
class ExampleAuthToken(APIView):
def post(self, request, format=None):
username = request.data.get("username")
password = request.data.get("password")
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User.objects.create_user(username=username)
user.set_password(password)
user.save()
content = {
'user': unicode(user.username),
'token': unicode(user.auth_token),
}
return Response(content)
In urls.py
urlpatterns = [
url(r'^authtoken/', ExampleAuthToken.as_view(), name='authtoken'),
]
To call using the angularjs;
var credentials = {
username: "root",
password: "root123"
};
$.post("http://localhost:8000/authtoken/", credentials {
success: function(data){
console.log(data);
}
}
I would definitely use a library. For token authentication there is the nifty django-rest-framework-jwt - it's straightforward to install and setup. To help with Angular JS looks like there is drf-angular-jwt (which uses DRF-JWT but I have not used it).