skip to Main Content

My set-up is as follows:

  • Flask app with custom domain and Nginx to make it more easy for HTTPS traffic
  • FastApi to which flask asks for data

I have a login page that returns an authorization token (maybe not the best for security but testing for small project) my login method in Flask is able to catch this auth_token and redirects to another page that with the auth_token as query parameters does something.

Code for each function is as follows:

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        # Forward the request to the FastAPI application running on port 8000
        try:
            user = request.form.get('user')
            password = request.form.get('password')
            # Make a request to the FastAPI endpoint
            response = requests.post('http://python-fastapi:8000/login', params={'user': user, 'password': password})
            response_data = response.json()
            if response.status_code != 200:
                # Catch detail in HTTPException
                raise Exception(response_data["detail"])
            return redirect("/dashboard?auth_token="+response_data["token"])
        except Exception as e:
            return jsonify({"status": "error", "message": str(e)}), 500
    else:
        return render_template("login.html")
@app.route("/dashboard")
def dashboard():
    auth_token = request.get("auth_token")
    # Get important information from the FastAPI application and render it in the dashboard
    try:
        response = requests.get('http://python-fastapi:8000/datasets', params={'auth_token': auth_token})
        if response.ok:
            data = response.json()
            return {"data": data}
        else:
            data = response.json()
            return {"error": data["detail"] + str(auth_token)}
    except Exception as e:
        return {"error": str(e)}

And if it is useful my nginx config:

server {
    listen 80;
    server_name automl.ddns.net;
    # This just redirects if you go through http
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name automl.ddns.net;
    resolver 127.0.0.11 valid=10s;
    resolver_timeout 5s;
    ssl_certificate /etc/letsencrypt/live/automl.ddns.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/automl.ddns.net/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    
    # Redirect any HTTP requests to HTTPS
    if ($scheme != "https") {
        return 301 https://$server_name$request_uri;
    }
    
    # Proxy requests to the Flask app running on port 5000
    location ~ ^/(.*)$ {
        proxy_pass http://python-flask:5000/$request_uri;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }


}

It is all mounted in Dockers with the following docker-compose.yml

version: '3.7'
services:
  mongo:
    image: mongo:latest
    container_name: mongodb
    restart: always
    ports:
      - "27017:27017"
    volumes:
      - ./data:/data/db
  python-fastapi:
    build:
      context: .
      dockerfile: DockerFile-fastapi
    container_name: python-fastapi
    restart: always
    command: python3 fastapi_client.py
    depends_on:
      - mongo
  python-flask:
    build:
      context: .
      dockerfile: DockerFile-flask
    container_name: python-flask
    restart: always
    command: python3 flask_app.py
    depends_on:
      - mongo
      - python-fastapi
      - nginx
  nginx:
    image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./myapp.conf:/etc/nginx/conf.d/myapp.conf
      - ./ssl:/etc/ssl
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
  mongo-express:
    image: mongo-express
    container_name: mongo-express
    restart: always
    ports:
      - "8081:8081"
    environment:
      ME_CONFIG_MONGODB_SERVER: mongodb
    depends_on:
      - mongo

It’s my first project building frontend with an API backend so apart from the problem I have any advice is appreciated

I have tried many changes in the nginx config and on how the redirect is performed from the login to the dashboard is performed but none works.

With debug I have checked that the URL that is getting to /dashboard does not have query_parameters making auth_token to be a None.

My idea is that nginx may be blocking something but I don´t actually know. For the ngix config I have tried to change the proxy_server to proxy_pass http://python-flask:5000/$1$is_args$args with no success.

EDIT 1:

I add to the question the corresponding FastAPI implementations.

# Create endpoint to get token after login
@app.post("/login")
async def login(user: str, password: str):
    # Check that username is alphanumeric
    if not user.isalnum():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username must be alphanumeric")
    # Check that password is alphanumeric
    if not password.isalnum():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Password must be alphanumeric")
    # Check that username is in the database
    if not users_collection.find_one({"user": user}): 
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username not found")
    # Check that password is correct
    if not users_collection.find_one({"user": user, "password": sha256(password.encode()).hexdigest()}): 
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Password is incorrect")
    # Change with time.time()
    token = sha256((user + password+str(time.time())).encode()).hexdigest()
    # Save the session in mongodb
    session_collection.insert_one({"token": token, "user": user})
    return {"status": "ok", "message": "User logged in successfully", "token": token}
@app.get("/datasets")
async def datasets(auth_token: str):
    if auth_token is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="You need to provide an auth_token")
    session = session_collection.find_one({"token": auth_token})
    # Must only get one user in session
    if len(session["user"]) != 1:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="There is more than one user in the session")
    for sess in session:
        user_id = sess["user"]
    datasets = datasets_collection.find({"user": user_id})
    # Check length of datasets
    if len(datasets) == 0:
        return []
    result = []
    for dataset in datasets:
        # Load pickle
        ds = Dataset.from_mongo(dataset["dataset"])
        result.append({"id": str(dataset["_id"]), "name": ds.name})

2

Answers


  1. Chosen as BEST ANSWER

    Okay, I was overthinking the problem.

    The actual problem was that the request in the HTML was not passing the query parameters.

    I feel so dumb.


  2. I re-wrote your login function with a few modifications and questions. This isn’t a full solution, but should get you on the right track.

    @app.route("/login", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            # don't wrap the entire thing into a try except block, makes debugging much harder if you don't know what
            # went wrong.
            #
            # don't use get() here - if the username and pass are missing, error. Defaults have no purpose here.
            user = request.form['user']
            password = request.form['password']
    
            # does fast api require a GET with params, or a POST with data?
            # if GET: requests.get(url, params=...)
            # if POST: requests.post(url, json=...)
            response = requests.post('http://python-fastapi:8000/login', json={'user': user, 'password': password})
            try:
                # error on any status errors, not just status != 200
                response.raise_for_status()
            except Exception:
                # just raise the original exception, or do something specific, but don't hide the explicit error
                raise
    
            # now you can try to access the response data
            response_data = response.json()
            # debugging: print(response_data)
            return redirect("/dashboard?auth_token="+response_data["token"])
        else:
            return render_template("login.html")
    

    The login function depends on user & password being set, so don’t fall back on defaults using the request.form.get() function. If those params are missing, exit with a specific error.

    I don’t know how your fast-api/login end point is configured, but params= are usually for GET requests, while POST uses data=/json=.

    The requests module contains a handy error catching function: raise_for_status. This will catch any "bad" responses, not just status != 200. It’s important, particularly in debugging, not to obscure the actual error. If you get error x, don’t catch it and log error y – log the actual error, so you know what actually went wrong.

    I’d do something similar in your dashboard() function as well – don’t obscure the actual error, print it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search