Flask-login works in development but not in production

71 Views Asked by At

Expected behavior:

  • User logs in and is redirected to the home page where user info is loaded from API and persisted

Error:

  • Getting 401 from API on the home page

I have created a relatively large app with Flask and I have used Flask-login and SQLAlchemy with MySQL. My database is on Avian, my backend is hosted on Heroku, and the frontend which is a react application is hosted on Vercel.

While I was testing my system in development there were no issues, however, after deploying to Heroku (I also tried with PythonAnywhere) there was a single issue. After logging in the user I get a status 200 successfully logged in, but after the user is redirected to the home page I get a 401 error from my API saying the user is not logged in.

What I have tried so far:

  • Using session type SQLAlchemy and connecting to my database to persist the session state
  • Adding session cookie domain
  • Using Flask-session
  • Adding Flask-tailsman to enforce HTTPS
  • Checking CORS settings and making sure to include all domains
  • Ensuring the user_loader is in order
  • Using web: gunicorn app:app --preload in the Procfile

Additional specs: Python 3.8, Flask 3.0.2

Heroku logs:

2024-03-15T05:51:18.006552+00:00 app[web.1]: 10.1.26.237 - - [15/Mar/2024:05:51:17 +0000] "OPTIONS /api/users/login HTTP/1.1" 200 0 "<my url>/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
2024-03-15T05:51:18.006707+00:00 heroku[router]: at=info method=OPTIONS path="/api/users/login" host=<heroku project>.herokuapp.com request_id=575d6668-8725-428c-b338-8fc83134e4bf fwd="60.254.90.89" dyno=web.1 connect=0ms service=2ms status=200 bytes=679 protocol=https
2024-03-15T05:51:19.955045+00:00 app[web.1]: 10.1.26.237 - - [15/Mar/2024:05:51:19 +0000] "POST /api/users/login HTTP/1.1" 200 38 "<my url>/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
2024-03-15T05:51:19.955217+00:00 heroku[router]: at=info method=POST path="/api/users/login" host=<heroku project>.herokuapp.com request_id=85ae383d-020b-40b5-94fd-968652d892d4 fwd="60.254.90.89" dyno=web.1 connect=0ms service=1627ms status=200 bytes=961 protocol=https
2024-03-15T05:51:21.549672+00:00 app[web.1]: 10.1.26.237 - - [15/Mar/2024:05:51:21 +0000] "OPTIONS /api/users/status HTTP/1.1" 200 0 "<my url>/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
2024-03-15T05:51:21.549804+00:00 heroku[router]: at=info method=OPTIONS path="/api/users/status" host=<heroku project>.herokuapp.com request_id=722dfc4b-e1df-4796-90c0-55ec73788ee3 fwd="60.254.90.89" dyno=web.1 connect=0ms service=2ms status=200 bytes=673 protocol=https
2024-03-15T05:51:21.871561+00:00 heroku[router]: at=info method=GET path="/api/users/status" host=<heroku>.herokuapp.com request_id=5e32fc4b-3011-4982-ae4d-bc4baf91b7b9 fwd="60.254.90.89" dyno=web.1 connect=0ms service=2ms status=401 bytes=569 protocol=https

Code (Minified):

  • create_app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_session import Session
import os
from flask_talisman import Talisman

# Sets up SQLAlchemy database
db = SQLAlchemy()

# Creates flask app
def create_app():
    app = Flask(
        __name__,
        static_url_path="",
        static_folder="../client/build",
        template_folder="build",
    )  # Initializes flask app

    CORS(app, origins=["<localhost>", "<my url>", "<my url with www>", "<vercel>", "<vercel1>", "<vercel2>"])

    app.config[
        "SQLALCHEMY_DATABASE_URI"
    ] = "mysql+pymysql://<avn database url>"
    app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")  # Secret key for flask app
    app.config["UPLOAD_FOLDER"] = "static/images"
    app.config['CORS_LOGGING'] = True
    db.init_app(app)
    app.config["SESSION_TYPE"] = "sqlalchemy"
    app.config["SESSION_SQLALCHEMY"] = db  # the SQLAlchemy object you have already created
    Session(app)
    app.config['SESSION_COOKIE_DOMAIN'] = '.<my url>'

    return app


# Initializes flask app for use in other files
app = create_app()

Talisman(app)

app.py

from flask_login import LoginManager
from server_module.create_app import app
from server_module.models import User

# Sets up login manager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "loginUser"


# User loader
@login_manager.user_loader
def load_user(id):
    user = User.query.get(id)
    return user


# Handles unauthorized access
@login_manager.unauthorized_handler
def unauthorized_handler():
    return "Unauthorized", 401


# Loads user
login_manager.user_loader(load_user)

models.py

import uuid
from sqlalchemy.sql import func
from werkzeug.security import generate_password_hash
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from sqlalchemy_serializer import SerializerMixin
from ..create_app import db

class User(db.Model, UserMixin, SerializerMixin):
    # These define columns of the User table
    id = db.Column(
        db.String(40),
        primary_key=True,
        nullable=False,
        default=lambda: str(uuid.uuid4()),
    )
    email = db.Column(db.String(150), nullable=False, unique=True)
    password = db.Column(db.String(500), nullable=False)
    
    def __init__(self, email, password):
        self.email = email
        self.password = generate_password_hash(password=password)

users.py

from flask import Blueprint, request
from flask_login import login_user, current_user
from ..models import User
from werkzeug.security import check_password_hash
from ..utility import error_msg

users_blueprint = Blueprint("users", __name__)

# The login API
@users_blueprint.route("/login", methods=["POST"])  # Completed
def loginUser():
    try:
        try:
            data = request.get_json()
        except:
            return error_msg()

        if not all(key in data for key in ("password", "email")):
            return error_msg()

        user = User.query.filter_by(email=data["email"]).first()
        if not user:
            return error_msg(404)

        if not check_password_hash(user.password, data["password"]):
            return {"msg": "Incorrect password"}, 400

        login_user(user, remember=True)  # Logs user in

        return {"msg": "User logged in successfully"}, 200
    except Exception as e:
        print("An error occurred: ", e)
        return error_msg(500, e)


# User status API, gets user status for the nav bar
@users_blueprint.route("/status", methods=["GET"])  # Completed
def getUserStatus():
    try:
        if current_user.is_authenticated:
            obj = current_user.to_dict(
                only=(
                    "username",
                    "type",
                    "status",
                    "avatar",
                    "id"
                )
            )
            return {"msg": obj}, 200
        else:
            return error_msg(401)
    except Exception as e:
        print("An error occurred: ", e)
        return error_msg(500, e)
0

There are 0 best solutions below