I'm using React + Django. Simple backend with one button on frontend handling POST requests:
function handleSubmit(e){
e.preventDefault();
axios.post(API_URL, {
forklift, battery
}).then((response) => {
console.log(response)
}).catch((error) => {
console.log(error.response)
});
}
The problem is when I try to submit the form while including .catch it always throws an 404 error on first request and then my development server on django crashes on second request (stops). When I delete catching error it works perfectly.
function handleSubmit(e){
e.preventDefault();
axios.post(API_URL, {
forklift, battery
}).then((response) => {
console.log(response)
});
}
My Django API_VIEW
#api_view(['POST'])
def start_charging(request):
if request.method == 'POST':
print('test')
forklift_ean = request.data['forklift']
battery_ean = request.data['battery']
try:
forklift = Forklift.objects.get(for_ean=forklift_ean)
battery = Battery.objects.get(bat_ean=battery_ean)
except Forklift.DoesNotExist or Battery.DoesNotExist:
return Response({'error_message': "No object error"},
status=status.HTTP_404_NOT_FOUND)
return Response({"message": "OK"})
Main url.py file
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('my_app.urls'))
]
App url.py file
urlpatterns = [
path('charging/', views.start_charging, name='start_charging'),
]
Error on first request:
xhr.js:220 POST http://127.0.0.1:8000/api/charging/ 404 (Not Found)
Error on seconds request (server crashes):
xhr.js:220 POST http://127.0.0.1:8000/api/charging/ net::ERR_CONNECTION_REFUSED
I am using corsheaders in my django project and the frontend is allowed to send requests:
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
]
Any idea why might be the reason for .catch to act that way?
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.
My application is laravel 8.75.0 in the backend serving apis requests for a react frontend.
I am using session to authenticate users, and to store session variables.
On production, react is built, and served statically using apache. Links are:
http://123.123.123.123/api -> backend laravel routes
http://123.123.123.123/dashboard -> react build folder
The production really doesn't use a domain. It is just an ip inside a corporate network, with no ssl.
On development, I use docker for hosting the laravel backend. React is served on its own. As a result, the links are:
http://172.18.0.3/api -> backend laravel routes
http://localhost:3000 -> react dev server
On successful login, I save session info like this on laravel:
$request->session()->put('workplace_id', $workplace_id);
session(['workplace_id' => $workplace_id]);
and on subsequent requests, I am getting the value like this:
Log::info("check session:".session('workplace_id'));
On production, I am able to get the workplace_id value. On development, I am not able to get the value because, from what I see, on every request to the api endpoint, a new session file is generated.
This is my code on react:
import axios from "axios";
import { wrapper } from 'axios-cookiejar-support';
import { CookieJar } from 'tough-cookie';
const jar = new CookieJar();
window.axios_client = wrapper(axios.create({ jar }));
window.axios_client.defaults.withCredentials = true;
window.axios_client.defaults.credentials = true;
window.axios_client.get(`${API_URL}/api/csrf-cookie`).then(response => {
console.log("csrf")
})
window.axios_client.interceptors.request.use((c) => {
const state = store.getState();
if (c.url && !c.url.includes(`${API_URL}/api`)) {
c.url = `${API_URL}/api` + c.url;
}
if (state.loginReducer && state.loginReducer?.appUser) {
const { token } = state.loginReducer.appUser;
c.headers.common.Authorization = `Bearer ${token}`;
c.withCredentials = true;
c.credentials = true;
}
return c;
});
window.axios_client.interceptors.response.use(
(response) => {
//const cookie = response.headers["set-cookie"][0];
return response;
},
(error) => {
if (error && error.response && error.response.status === 401) {
localStorage.removeItem("state");
window.location.href = window.location.origin;
}
return Promise.reject(error);
}
);
In the code above, I tried to use cookie jar that was recommended in some SO solutions, but it doesn't help.
I got to a point where I can see cookies sent to the server from the browser:
From the image, you can see there is an alert on the "SameSite" response. It says that this attempt was blocked because it had the "samesite=lax" though it came from a cross site response, which is not top level navigation.
You can see that the request cookies samesite are None. Also see laravel code below, and you can see that the configuration for samesite is set to none, and it still responds with lax. I don't know why it is happening.
Even though request cookies (XSRF-TOKEN & laravel_session) are always the same, the corresponding response cookies are changing on each response.
To get to this point I had to install self signed ssl certificate with a .local domain on the docker apache development server. These are the laravel files:
(relevant parts only)
config/session.php:
'secure' => env('SESSION_SECURE_COOKIE',true),
'http_only' => true,
'same_site' => "none",
config/cors.php:
'paths' => ['api/*','/login', '/logout', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => ["set-cookie"],
'max_age' => 0,
'supports_credentials' => true,
sanctum.php:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,localhost:3001,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
//'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
app/Http/Middleware/Cors.php:
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', $request->header('Access-Control-Request-Headers'));
Kernel.php:
'api' => [
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
There is a solution for this issue. It is explained in this medium article.
Copying here the required code changes:
React app.js:
axios.defaults.withCredentials = true;
Backend:
ini_set("session.cookie_domain", '.dev.local');
session_set_cookie_params(3600, '/', '.dev.local');
session_start();
$http_origin = $_SERVER['HTTP_ORIGIN'];
if ($http_origin == "http://dev.local:3000" || $http_origin == "http://localhost:3000"){
header("Access-Control-Allow-Origin: $http_origin");
}
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: X-Requested-With, Origin, Content-Type, X-Auth-Token, Accept');
And you need to use a example.local domain (set it in your host file).
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).
I have the following code in my react app:
I am sending an update request to rest backed which requires a user to be authenticated to perform PUT/POST/DELETE requests.
const update = (e) => {
e.preventDefault()
const formData = new FormData(form.current);
console.log('Token ' + localStorage.getItem("token")) // valid token
const requestOptions = {
method: 'PUT',
headers : {
// 'Authorization': 'Basic ' + btoa('user:password') // basic authentication works
"Authorization": 'Token ' + localStorage.getItem("token"),
},
body: formData
};
fetch(url, requestOptions)
.then(async response => {
const data = await response.json();
if(!response.ok){
const error = (data && data.message ) || response.status;
return Promise.reject(error)
}
alert('member updated')
history.push("/members")
})
.catch(error => console.error('Some error ', error))
}
Unfortunately, I'm getting these in console logs:
PUT http://localhost:8000/uskindia/56/ 403 (Forbidden)
Some error 403
And this in backed logs:
Forbidden: /uskindia/56/
[... *:*:*] "PUT /uskindia/56/ HTTP/1.1" 403 58
Trying to solve this for the last 24 hours but not getting it right.
From various tries, it seems like:
backend DRF and django-rest-auth is not handling token properly
tried various user agents like curl, httpie and postman to view request and response closely
Even in backed put logs, but request.user == AnonymousUser with token based authorisation.
works well with basic authorizatin, scheme.
if you are using djangorestframework for backend you must send token with this format :
"Authorization": 'Bearer ' + localStorage.getItem("token"),
use Bearer instead of token.
There was a typo in settings.py
# Earlier
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASS': [
'rest_framework.authentication.TokenAuthentication',
],
# ....
}
# Changed to
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
# ...
}
Thanks to Blog by Vitor Freitas
which made it clear that, if response contains
WWW-Authenticaate:Token then it means Token authentication was working.
As this was missing in my case, so I started all over setting REST_FRAMEWORK settings from scratch and found the root cause of issue.
I'm writing a simple web with Rocket as backend and React as frontend.
The code snippet looks like this for login page
#[post("/login", data = "<data>")]
pub fn login(
conn: DbConn,
mut cookies: Cookies<'_>,
data: Form<LoginForm>,
) -> Result<JsonValue, NotFound<String>> {
let valid_account = match Account::find_by_email(&*conn, data.email.as_str()) {
Ok(account) => {
if account.password == data.password {
account
} else {
return Err(NotFound("Incorrect email or password!".to_string()));
}
}
Err(_) => return Err(NotFound("Incorrect email or password!".to_string())),
};
cookies.add_private(
Cookie::build(AUTH_COOKIE, valid_account.id.to_string())
.same_site(rocket::http::SameSite::Strict)
.finish(),
);
Ok(json!({
"email": valid_account.email,
"name": valid_account.name,
}))
}
Code for main.rs
fn main() {
rocket::ignite()
.mount("/", routes![
account::login::login,
],
)
.register(catchers![errors::unauthorized])
.attach(rocket_cors::CorsOptions::default().to_cors().unwrap())
.manage(establish_connection())
.launch();
}
and code for React when trying to send the post request
export const postForm = async (
pathUrl: string,
postInfo: { [name: string]: any }
) => {
let axiosConfig = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Access-Control-Allow-Origin': '*',
},
};
try {
const response = await axios.post(
baseUrl + pathUrl,
querystringify.stringify(postInfo),
axiosConfig
);
return response.data as CurrentUser;
} catch (err) {
console.log(err);
return Promise.reject(err.response);
}
};
The code works fine it I enter the correct email and password.
However, it cannot capture the error message if I enter the wrong credentials.
Rocket log are the same between successful login and failure login which are
OPTIONS /login:
=> Error: No matching routes for OPTIONS /login.
=> Warning: Responding with 404 Not Found catcher.
=> CORS Fairing: Turned missing route OPTIONS /login into an OPTIONS pre-flight request
=> Response succeeded.
POST /login application/x-www-form-urlencoded:
=> Matched: POST /login (login)
=> Outcome: Success
=> Response succeeded.
and the error log in browser I captured was Error: "Request failed with status code 404" which was not the expected error message hard coded inside post function.
I believe it has something to do with Option or preflight processed inside Rocket which maybe in the purpose of security. But how can I suppress the system error and let my code to take over?
I have read previous SO post like state undefined: while sending post request from react app and GitHub issues like https://github.com/SergioBenitez/Rocket/issues/25. And still cannot find answer for my problem.
Thanks in advance!
Apparently I made several mistakes here due to unfamiliar with Rocket and React.
List here in case someone made the similar mistakes.
The 404 status code is from the first code snippets Result<JsonValue, NotFound<String>>. So if we write the return type as Result<JsonValue, Unauthorized<String>>, it would return 401 as unauthorized user.
Second, axios only receives json type and cannot parse string (correct me if I'm wrong). So we need to change the return type in server to Result<JsonValue, Unauthorized<JsonValue>>.