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
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.
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.The
login
function depends on user & password being set, so don’t fall back on defaults using therequest.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 usesdata=
/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 errorx
, don’t catch it and log errory
– 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.