Session
Closed this issue · 0 comments
Preamble:
First, I wanted to thank you for the work you have done with this framework, especially since it is as pleasant to use as its big brother Flask. Thank you!
Environment:
OS: Win 11
CPU: Intel i9-13900K
RAM: 32 GB
Python: 3.11.9
Virtual Environment: UV
Context:
I am working on a POC for an e-commerce site and have encountered several issues with the use of sessions. The first issue encountered is when I secured the header:
The application uses sub-applications, for example:
File: main.py
def create_app():
app = Microdot()
app.mount(auth, url_prefix='/auth')
return app
app = create_app()
Session(app, secret_key='top-secret')
Response.default_content_type = 'text/html'
Template.initialize(template_dir='templates/', enable_async=True)
I then modified the header response to add a bit of security:
File: main.py
@app.after_request
async def secure_headers(req, resp):
resp.headers['Cache-Control'] = "no-store, no-cache"
resp.headers['Content-Security-Policy'] = "base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'; img-src 'self'; media-src 'self'; object-src 'self'; script-src 'self'; worker-src 'self'"
resp.headers['Content-Security-Policy-Report-Only'] = "default-src 'self'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; object-src 'none';"
resp.headers['Cross-Origin-Embedder-Policy'] = "require-same-origin"
resp.headers['Cross-Origin-Opener-Policy'] = 'same-origin'
resp.headers['Cross-Origin-Resource-Policy'] = 'same-site'
resp.headers['Feature-Policy'] = "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; usb 'none'; encrypted-media 'none';"
resp.headers['Permissions-Policy'] = "accelerometer=(), ambient-light-sensor=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), speaker=(), sync-xhr=(), usb=(), vr=()"
resp.headers['Referrer-Policy'] = "strict-origin"
resp.headers['Set-Cookie'] = "; HttpOnly; Secure; SameSite=Lax"
resp.headers['Strict-Transport-Security'] = "max-age=31536000; includeSubDomains; preload"
resp.headers['X-Content-Type-Options'] = "nosniff"
resp.headers['X-DNS-Prefetch-Control'] = 'off'
resp.headers['X-Download-Options'] = 'noopen'
resp.headers['X-Frame-Options'] = "DENY"
resp.headers['X-Permitted-Cross-Domain-Policies'] = 'none'
resp.headers['X-Xss-Protection'] = "1; mode=block"
resp.headers.pop('X-Powered-By', None)
Now if I try to add values to the session in a post-login like this:
File: /auth/router.py
auth = Microdot()
@auth.post('/login')
@with_session
async def login(req, session):
...
# Add datas to session
session['user_id'] = str(datas['id'])
session['email'] = email
session['cart'] = datas['cart']
session.save()
return redirect('/', 303)
I get the following error, which is due to the modification of the resp.headers['Set-Cookie'] = "; HttpOnly; Secure; SameSite=Lax"
Traceback (most recent call last):
File "C:\Users\Administrator\Documents\POC\.venv\Lib\site-packages\microdot\microdot.py", line 1384, in dispatch_request
res = await invoke_handler(
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Administrator\Documents\POC\.venv\Lib\site-packages\microdot\microdot.py", line 25, in invoke_handler
ret = await asyncio.get_running_loop().run_in_executor(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python311\Lib\concurrent\futures\thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Administrator\Documents\POC\.venv\Lib\site-packages\microdot\session.py", line 97, in _update_session
response.set_cookie('session', encoded_session, http_only=True)
File "C:\Users\Administrator\Documents\POC\.venv\Lib\site-packages\microdot\microdot.py", line 610, in set_cookie
self.headers['Set-Cookie'].append(http_cookie)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'append'
I remind you that if I comment out line resp.headers['Set-Cookie'] = "; HttpOnly; Secure; SameSite=Lax"
, it works, but then I cannot secure my headers properly.
My second session problem is an issue of propagation. Here, the line resp.headers['Set-Cookie'] = "; HttpOnly; Secure; SameSite=Lax"
is commented out so that sessions can work.
When I am in my authentication and the user posts their data, the session is created correctly since I get the following response:
File: /auth/router.py
auth = Microdot()
@auth.post('/login')
@with_session
async def login(req, session):
...
# Add datas to session
session['user_id'] = str(datas['id'])
session['email'] = email
session['cart'] = datas['cart']
session.save()
print('Login page session (on sub-app auth):', session)
return redirect('/', 303)
The response is :
Login page session (on sub-app auth): {'user_id': '3c013fdb-111f-4a77-806a-dfbffc0073ad', 'email': 'user-test@test.fr', 'cart': '{"items":[]}'}
POST /auth/login 303
But after the redirect i come to the index page at '/'
File: main.py
def create_app():
app = Microdot()
app.mount(auth, url_prefix='/auth')
return app
app = create_app()
Session(app, secret_key='top-secret')
Response.default_content_type = 'text/html'
Template.initialize(template_dir='templates/', enable_async=True)
@app.get('/')
@with_session
async def home(req, session):
# Get db connection
async with Database() as con:
async with con.transaction():
datas = await con.fetch('SELECT "title", "link", "infos" FROM "ProductLines" ORDER BY "link";')
print('Home page session : (on app)', session)
return await Template('/home.jinja').render_async(
title='Bienvenue', product_lines=datas, base_url=getenv('BASE_URL'), session=session
)
I got an empty session response :
Home page session : (on app) {}
GET / 200
I conclude that the use of a session in a sub-app does not propagate correctly to the main application, which is quite inconvenient. I hope my explanations are clear enough.
Best regards.