ajaxflaskcookiescross-domain

Cookies not being set even with CORS enabled in Flask app


Please Note: there are many questions like this one but none of them have worked for me which is why I am asking this.

My REST API is a Flask application. My /login endpoint uses JWTs and sets the access and refresh tokens in the client's cookies.

I am running Flask on 127.0.0.1:5000 and the frontend on 127.0.0.1:8080.

Backend Code

@auth.api(
    http_path='/login',
    http_method='POST',
)
def login(email, password):
    # ...
    access_token = create_access_token(identity=user.id, fresh=True, expires_delta=app.config['JWT_ACCESS_TOKEN_EXP'])
    refresh_token = create_refresh_token(identity=user.id, expires_delta=app.config['JWT_REFRESH_TOKEN_EXP'])

    resp = jsonify({'login': True})
    set_access_cookies(resp, access_token)
    set_refresh_cookies(resp, refresh_token)
    # ...
    return resp

My app has CORS enabled:

from flask_cors import CORS
# ...
cors = CORS()

def create_app():
    app = Flask(__name__)
    # ...
    cors.init_app(app)
    # ...
    return app

Client Code

$.ajax({
    method: "POST",
    url: "http://127.0.0.1:5000/login",
    data: JSON.stringify({email: 'myemail', password: 'mypassword'}),
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    success: function (data) {
        console.log('logged in!');
    }
});

The Problem

The cookies are not being set in Firefox, Chrome nor Edge. However I can see the cookies in the Response Headers when using the browser's dev tools, but nothing appears in the Storage section, under Cookies.

enter image description here

I have tried multiple things:

Do I need some sort of proxy for the frontend? There must be an easier solution.


Solution

  • So after a lot of headaches, I figured out what's wrong. It's partially my fault but also a potential bug.

    I am using the flask-jwt-extended library for my JWT authentication; there are some environment variables regarding JWTs whose values are used in the set_access_cookies function.

    Typically, your Flask application config is the place for these variables, however, I had opted for using a separate .env file and loading my environment variables from it. The issue was that for things like MY_VAR=False, MY_VAR would get the string value "False" instead of a boolean flag.

    This manifested itself particularly poorly for JWT_COOKIE_SECURE - the value of this variable is used in the set_access_cookies function which itself uses the set_cookie function for a Request object - if you pass a string for the Secure field, it garbles the cookie and your browser will most likely ignore it.

    So a garbled cookie would look something like this:

    ('Set-Cookie', 'access_token_cookie=yourtoken; Domain=localhost.example.com; Secure; HttpOnly; Path=/')
    

    Notice how the Secure field is empty, when it should either be a boolean or not there at all (if false).

    I hope this helps!