Server-Side Scripting/Cookies and Sessions/Python (FastAPI)

main.py edit

# Demonstrates a server-side website using session variables.
#
# References:
#   https://fastapi.tiangolo.com/tutorial/bigger-applications/
#   https://fastapi.tiangolo.com/tutorial/static-files/
#   https://github.com/encode/starlette/blob/master/tests/middleware/test_session.py

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import uvicorn

from starlette.middleware.sessions import SessionMiddleware
import os

from routers import lesson12

app = FastAPI()

# 256-bit key secures session variables
app.add_middleware(SessionMiddleware, secret_key=os.urandom(32))

app.include_router(lesson12.router)

app.mount("/", StaticFiles(directory="static", html = True), name="static")

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=5000)

routers/lesson12.py edit

# Demonstrates session and cookie processing. The username is stored
# as a cookie and an internal userid is saved in a session variable.
# Also demonstrates secure password authentication using bcrypt salt
# and hash.
#
# References:
#   https://en.wikibooks.org/wiki/Python_Programming
#   https://fastapi.tiangolo.com/tutorial/cookie-params/
#   https://fastapi.tiangolo.com/advanced/response-cookies/
#   http://zetcode.com/python/bcrypt/

from fastapi import APIRouter, Request, Response
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates

import bcrypt

users = [
    # Password is the same as the username, just salted and hashed.
    # Don't do this in a production application! Use custom passwords.
    { "userid": 1, "username": "admin", 
        "password": b'$2b$12$6xEcJ9bCRo3JgNWyn32fwuSoRh1pg8f81jjHpYq6NQ9Y8uDkhWOE6'},
    { "userid": 2, "username": "test", 
        "password": b'$2b$12$UZLEFMg9ez.n88Sjpb/ZN.VVlmyPPxHOeL/DE452Si4H3PSQSB0Pa'}
]

router = APIRouter(prefix="/lesson12")
templates = Jinja2Templates(directory="templates")

@router.get("/", response_class=HTMLResponse)
async def get_lesson12(request: Request):
    username = request.cookies.get("username")
    userid = request.session.get("userid")
    return build_response(request, username, userid)

@router.post("/", response_class=HTMLResponse)
async def post_lesson12(request: Request, response: Response):
    username = request.cookies.get("username")
    userid = request.session.get("userid")

    form = dict(await request.form())
    
    if form.get("reload"):
        return RedirectResponse(request.url)
    
    if form.get("log-out"):
        request.session.clear()
        userid = None
        return build_response(request, username, userid)
    
    if form.get("forget-me"):
        request.session.clear()
        response = build_response(request, None, None)
        response.set_cookie("username", "", expires=0)
        return response
    
    if form.get("log-in"):
        username = form.get("username")
        password = form.get("password")
        userid = authenticate_user(username, password)
        if not userid:
            return RedirectResponse(request.url, status_code=303)

        request.session["userid"] = userid
        response = build_response(request, username, userid)
        response.set_cookie("username", username)
        return response

    return build_response(request, username, userid)

def build_response(request: Request, username, userid):
    cookie = str(bool(username))
    session = str(bool(userid))
    welcome = build_welcome(username, userid)
    if not username:
        username = ""

    return templates.TemplateResponse(
        "lesson12.html", 
        {   
            "request": request,
            "cookie": cookie,
            "session": session,
            "welcome": welcome,
            "username": username
        }
    )

def build_welcome(username, userid):
    if username and userid:
        welcome = f"Welcome back {username}! You are logged in."
    elif username:
        welcome = f"Welcome back  {username}! Please log in."
    else:
        welcome = "Welcome! Please log in."
    return welcome

def authenticate_user(username, password):
    for user in users:
        if user["username"] == username:
            result = bcrypt.checkpw(password.encode(), user["password"])
            if result:
                # should track successful logins
                return user["userid"]
            else:
                # Should track failed attempts, lock account, etc.
                return None
    return None

def generate_hashed_password(password):
    # Use this function to generate hashed passwords to save in 
    # the users list or a database.
    salt = bcrypt.gensalt()
    hashed = bcrypt.hashpw(password, salt)
    return hashed

templates/lesson12.html edit

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Lesson 12</title>
    <link rel="stylesheet" href="{{url_for('static', path='/styles.css')}}">
</head>

<body>
    <h1>Cookies and Sessions</h1>
    <p>Cookie: {{cookie}}<br>Session: {{session}}</p>
    <p>{{welcome}}</p>
    <hr>
    <form method="POST">
        <p><label for="username">Username:</label>
            <input type="text" id="username" name="username" value="{{username}}">
        </p>
        <p><label for="password">Password:</label>
            <input type="password" id="password" name="password">
        </p>
        <input type="submit" id="log-in" name="log-in" value="Log In">&nbsp;
        <input type="submit" id="log-out" name="log-out" value="Log Out">&nbsp;
        <input type="submit" id="forget-me" name="forget-me" value="Forget Me">&nbsp;
        <input type="submit" id="reload" name="Reload" value="Reload">
    </form>
</body>

</html>

Try It edit

See Server-Side Scripting/Routes and Templates/Python (FastAPI) to create a test environment.

See Also edit