I'm a novice web developer, but experienced python programmer, and Apache dolt. Recently, I've been tinkering with hosting a small website and learning my way through some hosting issues, Flask, html templates, etc.
I've followed several Flask tutorials about controlling access to pages with @login_required
decorators on access-controlled endpoints and using session
to store a logged in k-v pair. This all works perfectly when running locally on Flask's development server on my local machine. However, when I push this onto my hosting service, I'm getting what I believe is cached behavior to many of the access-controlled endpoints and I'm able to see them after logging out (and checking the session data to ensure the key is removed).
Some specifics...
Using flask
with session
for the login info, not flask-login.
Hosting on a managed VPS that is using Phusion Passenger as a WSGI interface to Apache
I have no config files in use for Apache...just defaults right now.
Website is very low traffic... Prolly just me & the bots right now. :)
My passenger_wsgi
file:
import sys, os
from datetime import timedelta
INTERP = "/home/<website>/venv1/bin/python3"
#INTERP is present twice so that the new Python interpreter knows the actual executable path
if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)
# def application(environ, start_response):
# start_response('200 OK', [('Content-type', 'text/plain')])
# return ["Hello, world!"]
sys.path.append(os.getcwd())
from app import app as application
After I login, things work as expected. After logout, I can still hit the endpoints that are supposed to be access-controlled and I repeatedly see this "evidence" when I inspect the network traffic in my browser:
Summary
URL: https://<website>/<endpoint> <---- an endpoint covered by @login_required
Status: 200
Source: Memory Cache
Request
No request, served from the memory cache.
Response
Content-Type: text/html; charset=utf-8
Expires: Wed, 22 Dec 2021 17:14:00 GMT
Date: Wed, 22 Dec 2021 17:04:00 GMT
Content-Length: 23
Cache-Control: max-age=600
Vary: User-Agent
Status: 200 OK
x-powered-by: Phusion Passenger 5.0.30
Server: Apache
So my questions are these...
flask-login
unless that is curative.I humbly submit my question to the folks who deal with these stacks!
Since 5.0, passenger will "helpfully" add cache-control headers to responses it deems 'cachable'.
In order to stop this, your application should add the header Cache-Control: no-store
.
To do this globally in Flask as described here:
@app.after_request
def add_header(response):
# response.cache_control.no_store = True
if 'Cache-Control' not in response.headers:
response.headers['Cache-Control'] = 'no-store'
return response
If you want to be more discriminating and only want to do this for routes which require a login, you could make your own extension to the login_required
decorator to do this for your routes which require logins (or a separate decorator entirely)
from flask import make_response
from flask_login import login_required as _login_required
def login_required(f):
# apply the usual login_required decorator
decorated = _login_required(f)
def cache_no_store_login_required_view(*args, **kwargs):
resp = make_response(decorated(*args, **kwargs))
# add the cache-control header to the response
resp.headers['Cache-Control'] = 'no-store'
return resp
return cache_no_store_login_required_view
Or as a separate decorator to set no-store...
from functools import wraps
def cache_no_store(f):
@wraps(f)
def decorated_view(*args, **kwargs):
resp = make_response(f(*args, **kwargs))
resp.headers['Cache-Control'] = 'no-store'
@app.route('/protected')
@cache_no_store
@login_required
def my_route():
# ...