Typorithm · medium

Flask: password verification and object ownership

pythonflaskindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Flask: password verification and object ownership

0%

Concept and contract

Separate establishing identity from authorizing a particular resource.

Authentication establishes who a requester is, while authorization decides whether that identity may access a resource. A successful login is not permission to read every record.

Flask provides session primitives, and Werkzeug provides maintained password-hashing helpers; the application still defines identity and ownership policy. This fixture uses fictional users and an owned key rather than an external identity provider.

Mechanism and worked example

The login endpoint verifies a generated password hash, clears previous session state and records the known fixture identity.

The login endpoint verifies a generated password hash, clears previous session state and records the known fixture identity. A subsequent detail request reads the signed session and compares the item owner before returning a title.

The owned collection contains one local and one foreign object. Returning 404 for foreign objects deliberately avoids confirming their existence to this caller.

Complete worked example

python

from flask import Flask, request, session, abort
from werkzeug.security import generate_password_hash, check_password_hash

def owner_request(password, item_id=1):
    app = Flask(__name__)
    app.config.update(TESTING=True, SECRET_KEY='owned-login-key')
    password_hash = generate_password_hash('course-password')
    items = {1: {'owner': 7, 'title': 'Mine'}, 2: {'owner': 8, 'title': 'Private'}}
    @app.post('/login')
    def login():
        if not check_password_hash(password_hash, request.form.get('password', '')):
            return {'error': 'invalid credentials'}, 401
        session.clear()
        session['user_id'] = 7
        return {'signed_in': True}
    @app.get('/items/<int:identifier>')
    def detail(identifier):
        user = session.get('user_id')
        if user is None:
            abort(401)
        item = items.get(identifier)
        if item is None or item['owner'] != user:
            abort(404)
        return {'title': item['title']}
    client = app.test_client()
    login_response = client.post('/login', data={'password': password})
    response = client.get(f'/items/{item_id}')
    return {'login': login_response.status_code, 'detail': response.status_code}
Reasoning
The login endpoint verifies a generated password hash, clears previous session state and records the known fixture identity. A subsequent detail request reads the signed session and compares the item owner before returning a title.

Counterexamples and common mistakes

The counterexample removes the owner comparison, allowing a valid login to expose another user’s private title.

The counterexample removes the owner comparison, allowing a valid login to expose another user’s private title. Checking only that a user is signed in would miss this bug.

Never accept a submitted owner identifier as the current identity, compare plaintext stored passwords or log credentials. Production login also needs CSRF protection, rate limiting and a coherent session lifecycle.

Counterexample: a runnable framework mistake

python

from flask import Flask, request, session, abort
from werkzeug.security import generate_password_hash, check_password_hash

def owner_request(password, item_id=1):
    app = Flask(__name__)
    app.config.update(TESTING=True, SECRET_KEY='owned-login-key')
    password_hash = generate_password_hash('course-password')
    items = {1: {'owner': 7, 'title': 'Mine'}, 2: {'owner': 8, 'title': 'Private'}}
    @app.post('/login')
    def login():
        if not check_password_hash(password_hash, request.form.get('password', '')):
            return {'error': 'invalid credentials'}, 401
        session.clear()
        session['user_id'] = 7
        return {'signed_in': True}
    @app.get('/items/<int:identifier>')
    def detail(identifier):
        user = session.get('user_id')
        if user is None:
            abort(401)
        item = items.get(identifier)
        if item is None:
            abort(404)
        return {'title': item['title']}
    client = app.test_client()
    login_response = client.post('/login', data={'password': password})
    response = client.get(f'/items/{item_id}')
    return {'login': login_response.status_code, 'detail': response.status_code}
Reasoning
The counterexample removes the owner comparison, allowing a valid login to expose another user’s private title. Checking only that a user is signed in would miss this bug.

Independent practice: one bounded framework change

Add a logout endpoint that clears the session.

Add a logout endpoint that clears the session. After reading the object once, call logout and request it again; include after_logout in the result.

Preserve password verification and ownership rejection. This small task tests the session lifecycle without introducing account registration, reset tokens or a custom cryptographic scheme. The combined capstone separately adds CSRF to browser writes.

Step 1: Add logout and verify the same resource is unauthorized afterward. Step 2: Clear session in a dedicated POST endpoint.

Practice starter: solve before reading the solution

python

from flask import Flask, request, session, abort
from werkzeug.security import generate_password_hash, check_password_hash

def owner_request(password, item_id=1):
    # Remove this placeholder, then adapt the retained baseline:
    # Add logout and verify the same resource is unauthorized afterward.
    raise NotImplementedError
    app = Flask(__name__)
    app.config.update(TESTING=True, SECRET_KEY='owned-login-key')
    password_hash = generate_password_hash('course-password')
    items = {1: {'owner': 7, 'title': 'Mine'}, 2: {'owner': 8, 'title': 'Private'}}
    @app.post('/login')
    def login():
        if not check_password_hash(password_hash, request.form.get('password', '')):
            return {'error': 'invalid credentials'}, 401
        session.clear()
        session['user_id'] = 7
        return {'signed_in': True}
    @app.get('/items/<int:identifier>')
    def detail(identifier):
        user = session.get('user_id')
        if user is None:
            abort(401)
        item = items.get(identifier)
        if item is None or item['owner'] != user:
            abort(404)
        return {'title': item['title']}
    client = app.test_client()
    login_response = client.post('/login', data={'password': password})
    response = client.get(f'/items/{item_id}')
    return {'login': login_response.status_code, 'detail': response.status_code}
Reasoning
Add logout and verify the same resource is unauthorized afterward. Clear session in a dedicated POST endpoint.
  • Hint 1: Request the object again with the same client.
  • Hint 2: Retain all existing authentication and ownership checks.

Solution, tests and trade-offs

Cases cover correct credentials for an owned item, wrong credentials and correct credentials for a foreign item.

Cases cover correct credentials for an owned item, wrong credentials and correct credentials for a foreign item. Practice also confirms that access is unauthorized after logout.

Observe actual client statuses rather than a local signed_in variable. The password hash is real, but the users and objects are disposable; passing this fixture does not establish a complete production authentication service.

Practice solution and acceptance checks

python

from flask import Flask, request, session, abort
from werkzeug.security import generate_password_hash, check_password_hash

def owner_request(password, item_id=1):
    app = Flask(__name__)
    app.config.update(TESTING=True, SECRET_KEY='owned-login-key')
    password_hash = generate_password_hash('course-password')
    items = {1: {'owner': 7, 'title': 'Mine'}, 2: {'owner': 8, 'title': 'Private'}}
    @app.post('/login')
    def login():
        if not check_password_hash(password_hash, request.form.get('password', '')):
            return {'error': 'invalid credentials'}, 401
        session.clear()
        session['user_id'] = 7
        return {'signed_in': True}
    @app.get('/items/<int:identifier>')
    def detail(identifier):
        user = session.get('user_id')
        if user is None:
            abort(401)
        item = items.get(identifier)
        if item is None or item['owner'] != user:
            abort(404)
        return {'title': item['title']}
    @app.post('/logout')
    def logout():
        session.clear()
        return {'signed_out': True}
    client = app.test_client()
    login_response = client.post('/login', data={'password': password})
    response = client.get(f'/items/{item_id}')
    client.post('/logout')
    after = client.get(f'/items/{item_id}')
    return {'login': login_response.status_code, 'detail': response.status_code, 'after_logout': after.status_code}
Reasoning
Cases cover correct credentials for an owned item, wrong credentials and correct credentials for a foreign item. Practice also confirms that access is unauthorized after logout.

Interview reasoning and next step

For a larger application a maintained extension such as Flask-Login can coordinate current_user and login-required behavior, while ownership predicates still belong in selected reads and writes..

For a larger application a maintained extension such as Flask-Login can coordinate current_user and login-required behavior, while ownership predicates still belong in selected reads and writes.

Consider session invalidation, credential recovery and audit policies explicitly. Keep authentication adapters separate from authorization rules so every resource path can be tested with the same owner, foreign-user and anonymous cases.

Optional stretch — after the core task works: Add bounded login attempts and explicit CSRF integration.

Computer practice: https://applydjinn.com/typorithm/python-frameworks-lab-20261105.zip. Install the pinned dependencies in an isolated environment, run verify.py --track flask, then edit the retained exercise baseline and check it with --practice fl-auth. The lab uses owned in-memory databases, local framework clients and tiny CPU tensors. It never reads the working ApplyDjinn database, contacts production feeds or downloads models.

Separate establishing identity from authorizing a particular resource.