What is the right way to specify headers using axios? - reactjs

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.

Related

Flask and React CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request

I am trying to connect my react frontend to my flask api backend. Note that flask-cors is already installed.
I initiated CORS as CORS(app) . the login passes but I keep getting this error when I go to project route:
Access to fetch at 'http://192.168.101.4:5000/project' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
My init.py file:
from flask import Flask, request, make_response
from .extensions import db, migrate, jwt, mail, CORS
from .models import TokenBlocklist
from .routes.base import base
from .routes.auth import auth
from .routes.core import core
from .routes.admin import admin
from .routes.user import user
from .routes.project import project
def create_app(config_file='config.py'):
app = Flask(__name__)
app.config.from_pyfile(config_file)
#jwt.token_in_blocklist_loader
def check_if_token_revoked(jtw_header, jwt_payload: dict)->bool:
jti = jwt_payload['jti']
token = db.session.query(TokenBlocklist.id).filter_by(jti=jti).scalar()
return token is not None
# initiate
db.init_app(app)
migrate.init_app(app, db)
jwt.init_app(app)
mail.init_app(app)
CORS(app)
app.register_blueprint(base)
app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(core, url_prefix='/core')
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(user, url_prefix='/user')
app.register_blueprint(project, url_prefix='/project')
return app
my auth.py:
"""
Route: endpoints for authentication
"""
from flask import Blueprint, jsonify, request, current_app, url_for, make_response
import datetime
import uuid
import validators
from itsdangerous import URLSafeTimedSerializer
from flask_jwt_extended import create_access_token, create_refresh_token, get_jwt_identity, jwt_required, unset_jwt_cookies, get_jwt, set_access_cookies, set_refresh_cookies
from werkzeug.security import generate_password_hash
from flask_cors import CORS
from ..utils import send_mail
from ..extensions import db, status, jwt
from ..models import User, TokenBlocklist
auth = Blueprint('auth', __name__)
CORS(auth)
#auth.route('/confirm-email/<token>')
def confirm_email(token):
s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
try:
email = s.loads(token, salt='email-confirm', max_age=200)
# mark user mail as confirmed
user = User.query.filter_by(email=email).first()
if not user.is_confirmed:
user.is_confirmed = True
db.session.commit()
return jsonify(msg='email confirmed'), status.ok
except:
return jsonify(msg = False), status.bad
#auth.route('/register', methods=['POST'])
def register():
"""
recieve request->username, email, password
check username & email exists or not, if not hash password and store.
send email with confirmation link
"""
data = request.get_json()
username = data['username']
email = data['email']
password = data['password']
# check existance of requirements in request json
if not username or not email or not password:
return jsonify(msg='missing json data'), status.bad
# email validations
if not validators.email(email):
return jsonify(msg='invalid email address'),
# check if username and email is taken
user = User.query.filter_by(username=username).first()
if user:
return jsonify(msg='username already taken')
user = User.query.filter_by(email=email).first()
if user:
return jsonify(msg='email already taken')
# save new user in db
new_user = User(
username = username,
email = email,
password = generate_password_hash(password, 'sha256'), # probably not a good practice to use User
public_id = uuid.uuid4()
)
try:
db.session.add(new_user)
db.session.commit()
except:
return jsonify(msg='could not save')
# create email confirmation link
token = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
token = token.dumps(email, salt='email-confirm')
link = url_for('auth.confirm_email', token=token, _external=True)
# send mail
body = f'confirmation link: {link}'
mail_sent = send_mail(recipient=email, body=body, subject='Linkage Email Confirmation')
if not mail_sent:
return jsonify(msg= 'email could not be sent')
return jsonify(msg= 'mail sent' ,email=email, token=token), 201
#auth.route('/login', methods=['POST'])
def login():
"""
recieve json data->email, password
check if email exists,
check user's hashed password and if email is confirmed.
generate refresh and access token and return
"""
data = request.get_json()
email = data['email']
password = data['password']
user = User.query.filter_by(email=email).first()
# email not found
if not user:
return make_response(
'User not found',
401,
{'WWW-Authenticate' : 'Basic realm ="could not verify"'}
)
# email found
verified = user.verify_password(password)
if verified is False:
return make_response(
'password mismatch',
401,
{'WWW-Authenticate' : 'Basic realm ="could not verify"'}
)
# authenticated, now generate tokens
user_public_id = user.public_id
refresh_token = create_refresh_token(identity=user_public_id)
access_token = create_access_token(identity=user_public_id)
# i am totally not sure about the following, but the fe dev *insists*
response = jsonify({"x-access-token": access_token})
response.set_cookie("refresh_token_cookie", refresh_token)
return response, 200
And Finally my project.py:
from datetime import datetime
from flask import Blueprint, jsonify, request, make_response
from flask_jwt_extended import get_jwt_identity, jwt_required
from ..extensions import db
from ..models import Project
project = Blueprint('project', __name__)
# this route must be deleted before production
#project.route('/', methods=['GET'])
#jwt_required()
def all_projects():
user_public_id = get_jwt_identity()
projects = Project.query.filter_by(user_public_id=user_public_id).all()
projects_data = []
for project in projects:
project_data = {}
project_data['id'] = project.id
project_data['domain'] = project.domain
project_data['name'] = project.name
project_data['user_public_id'] = project.user_public_id
project_data['wp_password'] = project.wp_password
project_data['wp_username'] = project.wp_username
project_data['date_added'] = project.date_added
projects_data.append(project_data)
return jsonify(projects = projects_data), 20
CORS related configs in my config.py file:
#CORS
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization']
CORS_SUPPORTS_CREDENTIALS = True
CORS_HEADERS = ['Content-Type', 'Authorization']
I am totally lost here. I don't know where is the redirect is happening.
Thank you for your time.

Problems with find_one_and_update using Pymongo/Flask with React application

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.

How to fix python flask cors issue using flask-cors library

I could really use some help. I can't figure out what I'm doing wrong. I keep getting
Edit : Frontend React application runs on localhost:3000, backend is running on localhost:5000
Access to XMLHttpRequest at 'http://localhost:5000/api/auth/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
def create_app(test_config=None):
logger = logging.getLogger(__name__)
logger.info("Flask App Starting")
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
CORS(app)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
logging.getLogger('flask_cors').level = logging.DEBUG
app.config.from_mapping(
SECRET_KEY="dev",
JWT_SECRET_KEY="super secret key",
JWT_ACCESS_TOKEN_EXPIRES=timedelta(hours=2),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile("config.py", silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
jwt = JWTManager(app)
"""
Adding blueprints
"""
from app.routes import tester
from app.routes import auth
from app.routes import api_routes
from app.routes import similar_routes
app.register_blueprint(tester.bp)
app.register_blueprint(auth.bp)
app.register_blueprint(api_routes.bp)
app.register_blueprint(similar_routes.bp)
#app.before_request
def check_login():
"""Before each request check if token exist."""
pass
logger.info("Checking if token is required")
if (not getattr(app.view_functions[flask.request.endpoint], "is_public", False)):
logger.info("Token required")
try:
result = verify_jwt_in_request(locations="headers")
logger.debug(f"Identity sent in is {get_jwt_identity()}")
except Exception as e:
logger.error("Error occured during checking token")
logger.error(e)
return jsonify(msg="Token Expired"),401
#app.errorhandler(Exception)
def all_exception_handler(error):
logger.error("Error caught" + str(error) )
return jsonify(msg="Oh no! A Server error occured. :,( "), 500
return app
if __name__ == "__main__":
loggingSetup()
app = create_app()
logger.info("App Created")
app.run(debug=True)
logger.info("App Running")
I'm making API calls from my react frontend, using axios
axios.defaults.baseURL = "http://localhost:5000/api"
function getHeaders(token){
return {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
"Authorization": "Bearer " + token,
'Access-Control-Allow-Origin': '*'
}
}
async function createCustomObject(token) {
let url = "/ontology/create-object";
let options = {
method: "POST",
url: url,
headers: getHeaders(token),
};
let response = await axios(options).then((response) => {
let data = response.data
}).catch((error) => {
handleError(error.response)
})
return response;
What am I missing?
You would set your origin to http://localhost:3000:
cors = CORS(app, resources={r"/api": {"origins": "http://localhost:3000"}})
'Access-Control-Allow-Origin': 'http://localhost:3000'
I resolved my issue using proxy after trying a couple of failed attempts using CORS solution.
I simply put "proxy": "http://127.0.0.1:5000" in my package.json and therefore, I can then use
fetch(`/test`)
.then((res) => res.json())
.then((data) => {
//do something
});
easily in my app without actually providing the full url to the backend (http://127.0.0.1:5000).

Cookies not sending in cross-origin react-flask app

I am currently building a ReactJS (front at "https://www.example.com") Flask (back "https://server.example.com") app. The react client makes GET and POST requests to my flask server, but first, the requests must be authenticated. I am using Flask-CORS to only accept requests from "https://www.example.com". I have enabled "CORS_SUPPORTS_CREDENTIALS" on my server, and from the client side, I have set fetch requests to "credentials: 'include'".
server.py
import os
import functools
from flask import Flask, Blueprint, request, make_response
from flask_cors import CORS
from .config import Config
# Application factory pattern
def create_app():
config_object = Config()
app = Flask(__name__)
# Cross-Origin Config
CORS(app,
origins=[config_object.CORS_ALLOW_ORIGIN], # https://www.example.com
supports_credentials=config_object.CORS_SUPPORTS_CREDENTIALS # True
)
app.config.from_object(config_object)
app.register_blueprint(main)
return app
# Cookie authentication
def auth_required(view):
#functools.wraps(view)
def wrapped_view(**kwargs):
user_cookie = request.cookies.get('USER_ID_COOKIE')
session_cookie = request.cookies.get('USER_SESSION_COOKIE')
is_authenticated = verify_user_session(user_cookie, session_cookie) # T/F if this is a valid user
if is_authenticated:
# Continue to route...
return view(**kwargs)
else:
# Reject request...
response = make_response({"flash": "User not logged in."}, 403)
return response
return wrapped_view
bp = Blueprint('main', __name__)
#bp.get('/main/protected')
#auth_required
def get_protected_data():
response = make_response({"fakeData": "Do you believe in life after love?"}, 200)
return response
app = create_app()
if __name__ == "__main__":
app.run()
snippet_from_protected_client.js
const MainPage = ({ setFlashMessages }) => {
let statusOK;
let statusCode;
const isStatusOK = (res) => {
statusOK = res.ok;
statusCode = res.status;
return res.json();
}
const [data, setData] = useState(null);
useEffect(() => {
fetch(process.env.REACT_APP_SERVER + '/main/protected', { // REACT_APP_SERVER=https://server.example.com
credentials: 'include'
})
.then(isStatusOK)
.then(data => {
if (statusOK) {
setData(data.fakeData);
} else if (statusCode === 403) {
setFlashMessages(data.flash);
}
});
}, []);
return (
<main>
<div className="container-md">
<div className="row">
<div className="col-md-1"></div>
<div className="col-md-11 mt-5">
<h1 className="text-start">My Account</h1>
<p className="text-start">Just some filler text here.</p>
<p className="text-start">{ data }</p>
</div>
</div>
</div>
</main>
);
}
export default MainPage;
Hopefully you get the idea--I've abbreviated a lot but maintained the main features that are giving me the issue. So now, the issue:
My cookies, which are have been created and are held in the browser, have been set with the JS library js-cookie like so:
snippet_from_login_client.js
Cookies.set('USER_ID_COOKIE', '1', { sameSite: 'none', secure: true}) // again, example
Cookies.set('USER_SESSION_COOKIE', 'ARanDomStrInGSetDURingLoGiNANDSTOrEDinTHESERVERDB', { sameSite: 'none', secure: true}) // again, example
And I know that they are set because I can see them in the developer tools. However, on subsequent requests to protected routes, the server accepts the request (meaning it's not a CORS origin issue) but #auth_required throws 403 (as shown above). After checking the Request headers in my browser's development tools, I can see that the request did not send with the cookies!
REQUEST HEADER FROM BROWSER DEVELOPER TOOLS
* Accept: */*
* Accept-Encoding: gzip, deflate, br
* Accept-Language: en-US,en;q=0.9
* Connection: keep-alive
* Host: server.example.com
* Origin: https://www.example.com
* Referer: https://www.example.com/
* Sec-Fetch-Dest: empty
* Sec-Fetch-Mode: cors
* Sec-Fetch-Site: cross-site
* Sec-GPC: 1
* User-Agent: *DEVICE-DATA*
Notice no Cookie: -- header despite the cookies being set...
Why aren't my cookies sending? Any tips or leads would be incredibly helpful.
I figured out the problem! I should have set my cookie domain to '.example.com' so that the client would attach them to each request. Here is how you would do it with js-cookie:
Cookies.set('USER_ID_COOKIE', '1', { domain: '.example.com', sameSite: 'none', secure: true}) // again, example
I'm not sure how to send cookies between unrelated domains, but if your server and client are formatted as 'server.domain.com' and 'client.domain.com', then this solution should work for you. Hopefully, this helps someone out there!

Angular 2 POST giving 403 CSRF error posting to Django server

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.

Resources