🔐 Session Management

Lila provides a simple yet powerful session management system built on top of itsdangerous for cryptographically signed session data. This ensures that session data cannot be tampered with on the client side.

The Session class provides a set of static methods to handle session data.

📝 Setting Session Data

The setSession method is used to store session data in a cookie. It can handle strings, dictionaries, and lists, which are automatically serialized to JSON.

setSession example

from lila.core.session import Session
from lila.core.responses import JSONResponse

@router.route("/set-session", methods=["GET"])
async def set_session_example(request: Request):
  response = JSONResponse({"message": "Session has been set"})
  session_data = {"user_id": 123, "username": "lila_user"}
  Session.setSession(new_val=session_data, response=response, name_cookie="user_session")
  return response
          

The setSession method accepts several parameters to customize the cookie, such as secure, samesite, max_age, etc.

🔍 Getting Session Data

The getSession method retrieves the raw, signed session data from a cookie. To get the unsigned and validated data, use getSessionValue or unsign.

✅ Unsigning and Validating Session Data

The unsign and getSessionValue methods get the session data in its original form after verifying the signature. getSessionValue is recommended because it also gracefully handles expired signatures.

getSessionValue example

from lila.core.session import Session

@router.route("/get-session", methods=["GET"])
async def get_session_example(request: Request):
  session_data = Session.getSessionValue(request=request, key="user_session")
  if session_data:
    return JSONResponse({"session_data": session_data})
  else:
    return JSONResponse({"message": "No session found or session is invalid/expired."}, status_code=401)
          

🗑️ Deleting a Session

The deleteSession method is used to delete a session cookie from the client's browser.

deleteSession example

from lila.core.session import Session
from lila.core.responses import JSONResponse

@router.route("/logout", methods=["GET"])
async def logout(request: Request):
  response = JSONResponse({"message": "Logged out successfully"})
  Session.deleteSession(response=response, name_cookie="user_session")
  return response
          

🚀 Simplified Async Helpers

For convenience, Lila provides async static methods in the Session class that simplify common operations.

Simplified Helpers

from lila.core.session import Session

# Get session data
data = await Session.get(request, key="auth")

# Set session data (requires response)
await Session.set(request, response, data={"id": 1}, key="auth")

# Delete session data
await Session.delete(response, key="auth")
          

📖 Full Session Class Reference

Here is the full code for the Session class for your reference.

lila/core/session.py

import orjson
from app.config import SECRET_KEY
from itsdangerous import BadSignature, URLSafeTimedSerializer, SignatureExpired
from core.request import Request
from typing import Union, Optional, Dict, List
from core.logger import Logger

serializer = URLSafeTimedSerializer(SECRET_KEY)

class Session:
    @staticmethod
    def setSession(
        new_val: str | dict | list,
        response,
        name_cookie: str = "session",
        secure: bool = True,
        samesite: str = "strict",
        max_age: int = 3600,
        domain: Optional[str] = None,
        http_only: bool = True,
        path: str = "/",
    ) -> bool:
        try:
            if isinstance(new_val, (dict, list)):
                new_val = orjson.dumps(new_val).decode()
            else:
                new_val = str(new_val)

            signed_session = serializer.dumps(new_val)

            response.set_cookie(
                key=name_cookie,
                value=signed_session,
                max_age=max_age,
                expires=max_age,
                secure=secure,
                httponly=http_only,
                samesite=samesite,
                domain=domain,
                path=path,
            )
            return True
        except (TypeError, ValueError, Exception) as e:
            Logger.error(f"Error setting session: {str(e)}")
            return False

    @staticmethod
    def getSession(key: str, request: Request) -> Optional[str]:
        return request.cookies.get(key)

    @staticmethod
    def unsign(
        key: str, request: Request, max_age: Optional[int] = None
    ) -> Union[Dict, List, str, None]:
        """Unsign and return the session data for the given cookie key."""
        session_data = request.cookies.get(key)
        if not session_data:
            return None

        try:
            unsigned_data = serializer.loads(session_data, max_age=max_age)
            try:
                return orjson.loads(unsigned_data)
            except orjson.JSONDecodeError:
                return unsigned_data

        except SignatureExpired:
            Logger.warning(f"Session expired for cookie: {key}")
            return None
        except BadSignature:
            Logger.warning(f"Invalid session signature for cookie: {key}")
            return None
        except Exception as e:
            Logger.error(f"Error unsigning session: {str(e)}")
            return None

    @staticmethod
    def deleteSession(response, name_cookie: str) -> bool:
        try:
            response.delete_cookie(name_cookie)
            return True
        except Exception as e:
            Logger.error(f"Error deleting session: {str(e)}")
            return False

    @staticmethod
    def getSessionValue(
        request: Request, key: str = "auth", max_age: Optional[int] = 3600
    ) -> Union[Dict, List, str, None]:
        """Convenience wrapper around unsign with a default max_age."""
        return Session.unsign(key=key, request=request, max_age=max_age)

    @staticmethod
    async def get(request: Request, key: str = "auth") -> Union[Dict, List, str, None]:
        """Async helper to get session data."""
        return Session.unsign(key=key, request=request)

    @staticmethod
    async def set(
        request: Request, response, data: dict, key: str = "auth", max_age: int = 3600
    ) -> bool:
        """Async helper to set session data."""
        return Session.setSession(new_val=data, response=response, name_cookie=key, max_age=max_age)

    @staticmethod
    async def delete(response, key: str = "auth") -> bool:
        """Async helper to delete session data."""
        return Session.deleteSession(response=response, name_cookie=key)