pythonflaskaxioscorsngrok

CORS header 'Access-Control-Allow-Origin' is ‘*’ (problem may be because of withCredentials: true)


RESOLVED

When I did all of the listed items below AND had this code for my CORS configuration in my app.py:

CORS(app, supports_credentials=True, origins=[
    "http://localhost:3000",
    "http://thecasecomp.com"
])

Seems like I needed my online domain as well for both to work.

I'm getting the error below when trying to get information from my Flask API backend using Axios.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘https://453c-162-199-93-254.ngrok-free.app/@me’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’)

My backend is hosted at http://localhost:5000, and my ReactJS frontend is hosted at http://localhost:3000.

  1. All my requests to my backend work when I use the direct localhost 5000 backend address. However, I need to use ngrok to create a public address that accesses my localhost (because I want to host the backend on my computer). The ngrok address is accessible on a different network and returns user: Unauthorized.

  2. When I use the ngrok address (e.g., https://453c-162-199-93-254.ngrok-free.app) in the axios request, it sends the error above.

  3. I've tried using axios.get directly on the front end instead of using an instance of it (axios.create from httpClient). I assume I need to use withCredentials because I have a registration and login (unless there's another way?). I also tried adding axios.defaults.withCredentials = true before creating the instance.

  4. I've tried adding origins when doing CORS(app, origins=[http://localhost:3000]).

  5. I've tried adding @cross_origins(origins=[...]) as well.

  6. I've tried bypassing the ngrok browser warning, and that led to the error disappearing. However, my GET request from /@me was still not working, even after apparently logging in. After using axios directly with the withCredentials option, the error returned (see below).

    const resp = await axios.get(`${backendLink}/@me`, withCredentials: true,});

  7. I've tried defining a proxy ("proxy": "https://fe10-162-199-93-254.ngrok-free.app") and added more configuration (see below).

    app.config["SESSION_COOKIE_SAMESITE"]="None" app.config["SESSION_COOKIE_SECURE"]=True

My app.py code:

from flask import Flask, request, abort, jsonify, session
from flask_bcrypt import Bcrypt
from flask_session import Session
from flask_cors import CORS, cross_origin
from config import ApplicationConfig
from models import db, User

app = Flask(__name__)
app.config.from_object(ApplicationConfig)

bcrypt = Bcrypt(app)
CORS(app, supports_credentials=True, origins=[
    "http://localhost:3000",
    "http://localhost:5000",
    "https://453c-162-199-93-254.ngrok-free.app",
])
server_session = Session(app)
db.init_app(app)

with app.app_context():
    db.create_all()

@app.route("/@me")
def get_current_user():
    user_id = session.get("user_id")

    if not user_id:
        return jsonify({"error": "User unauthorized."}), 401

    user = User.query.filter_by(id = user_id).first()
    
    if user is None:
        return jsonify({"error": "User not found."}), 404

    return jsonify({
        "id": user.id,
        "email": user.email,
        "first_name": user.first_name,
        "last_name": user.last_name,
        "phone_number": user.phone_number,
        "grade": user.grade,
        "school": user.school
    })
    
@app.route("/registration", methods=["POST"])
def register_user():
    email = request.json["email"]
    password = request.json["password"]
    first_name = request.json["firstName"]
    last_name = request.json["lastName"]
    phone_number = request.json["phone"]
    grade = request.json["grade"]
    school = request.json["school"]

    user_exists = User.query.filter_by(email = email).first() is not None

    if user_exists:
        return jsonify({"error": "User already exists."}), 409
    
    hashed_password = bcrypt.generate_password_hash(password)
    new_user = User(email = email, password = hashed_password, first_name = first_name, last_name = last_name, phone_number = phone_number, grade = grade, school = school)
    db.session.add(new_user)
    
    db.session.commit()

    session["user_id"] = new_user.id

    return jsonify({
        "id": new_user.id,
        "email": new_user.email,
        "first_name": new_user.first_name,
        "last_name": new_user.last_name,
        "phone_number": new_user.phone_number,
        "grade": new_user.grade,
        "school": new_user.school
    })

@app.route("/remove-user", methods=["POST"])
def remove_user():
    user_id = request.json["user_id"]

    if not user_id:
        return jsonify({"error": "User ID not found."}), 404

    user = User.query.filter_by(id=user_id).first()
    if user:
        db.session.delete(user)
        db.session.commit()
        return jsonify({"message": "User removed successfully."}), 200

@app.route("/login", methods=["POST"])
def login_user():
    email = request.json["email"]
    password = request.json["password"]

    user = User.query.filter_by(email = email).first()

    if user is None:
        return jsonify({"error": "User unauthorized."}), 401
    
    if not bcrypt.check_password_hash(user.password, password):
        return jsonify({"error": "User unauthorized."}), 401

    session["user_id"] = user.id

    return jsonify({
        "id": user.id,
        "email": user.email,
    })

@app.route("/logout", methods=["POST"])
def logout_user():
    session.pop("user_id")
    return "200"

if __name__ == "__main__":
    app.run(debug = True)

My httpClient.js code:

import axios from "axios";

axios.defaults.withCredentials = true;

export default axios.create({
  withCredentials: true,
});

My App.js code:

import "./App.css";

import "./styles/Fonts.css";

import Navbar from "./components/Navbar.js";
import Footer from "./components/Footer.js";

import Home from "./pages/Home";
import Timeline from "./pages/Timeline";
import Registration from "./pages/Registration";
import Submission from "./pages/Submission";
import Contact from "./pages/Contact";
import About from "./pages/About";
import Login from "./pages/Login";
import Profile from "./pages/Profile";

import React, { useState, useEffect } from "react";
import httpClient from "./util/httpClient.js";
import styled from "styled-components";
import { HashRouter as Router, Route, Routes } from "react-router-dom";
import backendLink from "./util/backendLink.js";
import axios from "axios";
axios.defaults.withCredentials = true;

const AppStyle = styled.div`
  text-align: center;
  font-family: "Regular";
`;

function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    (async () => {
      try {
        const resp = await httpClient.get(`${backendLink}/@me`, {
          withCredentials: true,
        });
        setUser(resp.data);
      } catch (error) {
        console.log("Unauthenticated");
      }
    })();
  }, []);

  return (
    <AppStyle>
      <Router>
        <Navbar />
        <Routes>
          <Route path="/" exact element={<Home />} />
          <Route path="/timeline" exact element={<Timeline />} />
          <Route path="/registration" exact element={<Registration />} />
          <Route path="/submission" exact element={<Submission />} />
          <Route path="/contact" exact element={<Contact />} />
          <Route path="/about" exact element={<About />} />
          <Route path="/login" exact element={<Login />} />
          {user != null ? (
            <Route path={`/profile/${user.id}`} exact element={<Profile />} />
          ) : (
            <></>
          )}
          {/* TODO: add 404 not found page, fix fade out animation for alerts */}
        </Routes>
        <Footer />
      </Router>
    </AppStyle>
  );
}

export default App;

My config.py code:

from dotenv import load_dotenv
import os
import redis

load_dotenv()

class ApplicationConfig:
    SECRET_KEY = os.environ["SECRET_KEY"]

    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_ECHO = True
    SQLALCHEMY_DATABASE_URI = r"sqlite:///./db.sqlite"

    SESSION_TYPE = "redis"
    SESSION_PERMANENT = False
    SESSION_USE_SIGNER = True
    SESSION_REDIS = redis.from_url("redis://127.0.0.1:6379")
    SESSION_COOKIE_SAMESITE = "None"
    SESSION_COOKIE_SECURE = True

Edit:

  1. I updated my httpClient.js (see above).
  2. Registration, logging in, profile, and user data requests all work. But only on my localhost.
  3. Building and copying it into my Apache web server, which is fully set up on the domain http://thecasecomp.com, the CORS error (see below) returns.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘https://fe10-162-199-93-254.ngrok-free.app/@me’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).


Solution

  • When I did all of the listed items below AND had this code for my CORS configuration in my app.py:

    CORS(app, supports_credentials=True, origins=[
        "http://localhost:3000",
        "http://thecasecomp.com"
    ])
    

    Seems like I needed my online domain as well for both to work.