Skip to content

Authentication

Authentication is required to ensure that the client is who they claim to be, and for our project to ensure that each user only gets to see their own todos and only after they've proved they are the user. This is typically achieved by the user entering a username and password which are then checked against stored versions.

We will need to authenticate every request the user makes to the backend, however we ideally only want the user to enter their username and password once (until they logout). We can acheive this by saving information to a cookie when the user logs in, as the browser will then send us the cookie with every request.

We will need to save a piece of identifying information to the cookie when the user logs in (starts a session), for example a user ID. We can then read the cookie on every request and identify which user it is. However, cookies can be edited, or faked, by the client so we need to ensure that the information in the cookie hasn't been tampered with.

We can prevent tampering by signing the information in the cookie. Signing is where a cryptographic function is applied to the data using a secret key to create a signature. This signature is then stored with the data, allowing the stored signature to be checked against a recalculated version.

Quart-Auth can be used to manage the cookies. Quart-Auth will sign the data, and help ensure that the cookies are securely stored in the browser.

Run this command in backend/

poetry add quart-auth

Then by activating the AuthManager when creating the app in backend/src/backend/run.py

from quart_auth import AuthManager

auth_manager = AuthManager()

def create_app():
    ...
    auth_manager.init_app(app)
    ...

Whilst Quart-Auth comes with a sensible set of defaults for securing the cookie, our usage allows us to be more secure. In addition, however, we will need to disable some of the security for development.

Firstly to help development we will disable the secure cookie flag, as we aren't using HTTPS in development. In addition we'll allow the cookie name to be customised, so that we can use the __Host- prefix in production. To do so add the following to backend/src/backend/run.py,

def create_app() -> Quart:
    ...
    for key in [..., "QUART_AUTH_COOKIE_NAME"]:
        ...
    app.config["QUART_AUTH_COOKIE_SECURE"] = os.environ["QUART_AUTH_COOKIE_SECURE"] == "true"
    ...

and the following to the configuration files,

QUART_AUTH_COOKIE_NAME = "tozo-session"
QUART_AUTH_COOKIE_SECURE = false
QUART_AUTH_COOKIE_NAME = "tozo-session"
QUART_AUTH_COOKIE_SECURE = false
QUART_AUTH_COOKIE_NAME = "tozo-session"
QUART_AUTH_COOKIE_SECURE = false

Finally we can utilise the Strict samesite setting, rather than the Lax that Quart-Auth defaults to. This is because we only need to authenticate non-navigation requests to the API routes. We can do this by adding the following to backend/src/backend/run.py,

def create_app() -> Quart:
    ...
    app.config["QUART_AUTH_COOKIE_SAMESITE"] = "Strict"

JSON error responses

User attempts to access login_required routes without valid credentials result in an Unauthorized exception being raised. This by default results in a HTML response, whereas we need a standardised JSON based response. We can achieve this via an error handler by adding the following to backend/src/backend/run.py,

...
from werkzeug.exceptions import Unauthorized

def create_app() -> Quart:
    ...

    @app.errorhandler(Unauthorized)
    async def handle_unauthorized(_: Unauthorized) -> ResponseReturnValue:
        return {"code": "UNAUTHORIZED"}, 401