Authn/Z for my apps


Much of my focus has been on Household lately. I’ve tried to update all of them to be compatible with iOS26. This post though is not about iOS26 but wanted to instead focus it a bit more on the backend side.

For context, all of the backend for most of my apps follow this cookie cutter template. I built the skeleton in Flask and just have them run on uwsgi workers backed by Nginx. Its easy and makes me iterate faster. One of the best parts of choosing such a simple stack is, I can rely on Claude Code to build most of it since the structure is in place. Relying on a generic framework and folder structure also helps with generating CLAUDE.md files for my projects which further increases my iteration speed.

Authentication

All of my apps rely on Firebase for user authentication. Its fast and easy. I mostly end up using Sign in with Apple since its anonymized, and I don’t really care on collecting PII information. Once an user is setup, I build up this AppUser object on my apps that just gets passed on to all views as Singletons. This is the place to host all user related configs, check if they are paid or not etc. It makes it seamless since any background updates with the ObservedObject pattern lets me control UI on AppUser property changes.

For backend, I continue to rely on Firebase. Every request gets the firebase token passed in as the autorization header. This token it then verified back in my app, to make sure its a valid token and an existing user. All routes are configured seamless by using a decorator. Here’s a snippet

def verify_firebase_id_token(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'Authorization' not in request.headers:
            return jsonify({'error': 'Authorization header is expected'}), 401

        auth_header = request.headers['Authorization']
        id_token = auth_header.split(" ").pop()

        try:
            decoded_token = auth.verify_id_token(id_token)
            request.user = decoded_token
        except Exception as e:
            logging.error(f"Error in verify_firebase_id_token: {e}")
            return jsonify({'error': str(e)}), 401
        
        return f(*args, **kwargs)
    return decorated_function

Authorization

For Authz, the story is a bit more complicated. When I started building iOS projects, I used to rely on Firebase security rules. I moved on to just using Postgres for all my projects, since it ends up working fine for my use-case. Postgres offers row level security but the best way I found to enforce authorization is to use some sort of ACL tables that you create when a user onboards.

For example, in my household app — every user belongs to a household. They have permissions to create chores, recipes or meal plans under the household. This is set when the household is created and members of a household are onboarded. I call it the roles table. This is the main table I use to enforce users can only manipulate objects within that household.

def authorize_household_access(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not hasattr(request, 'user'):
            return jsonify({'error': 'User not authenticated'}), 401

        user_id = request.user.get('uid')
        household_id = None
        print(f"Authorizing user {user_id} for household access with kwargs: {kwargs} and args: {request.args}")

        # Check for household_id in path variables
        if 'household_id' in kwargs:
            household_id = kwargs['household_id']
        # Check for household_id in query params
        elif request.args.get('household_id'):
            household_id = request.args.get('household_id')
        # Check for household_id in POST/PUT request body
        elif request.method in ['POST', 'PUT', 'PATCH'] and request.is_json:
            data = request.get_json()
            household_id = data.get('household_id')

        if not household_id:
            return jsonify({'error': 'household_id is required'}), 400

        # Verify user has access to this household
        try:
            # Execute your query here.

            if not response.data:
                logging.warning(f"User {user_id} attempted to access household {household_id} without permission")
                return jsonify({'error': 'Access denied to this household'}), 403

        except Exception as e:
            logging.error(f"Error in authorize_household_access: {e}")
            return jsonify({'error': 'Authorization check failed'}), 500

        return f(*args, **kwargs)
    return decorated_function

These 2 snippets have served me decently, just make sure the chain of calls is authn first followed by authz. This ensures that the correct data is being retrieved for the users.