Search code examples
reactjsflaskcookiesfetch-apicredentials

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.


Solution

  • 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!