Complete worked example
python
from flask import Flask, request, session, abort
from flask_wtf.csrf import CSRFProtect, generate_csrf
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import select, String
from sqlalchemy.orm import Mapped, mapped_column
def owned_note(title, token_mode='correct'):
app = Flask(__name__)
app.config.update(TESTING=True, SECRET_KEY='owned-capstone-key', SQLALCHEMY_DATABASE_URI='sqlite:///:memory:')
CSRFProtect(app)
db = SQLAlchemy(app)
class Note(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
owner: Mapped[int] = mapped_column(nullable=False)
title: Mapped[str] = mapped_column(String(20), nullable=False)
password_hash = generate_password_hash('course-password')
@app.get('/token')
def token():
return {'token': generate_csrf()}
@app.post('/login')
def login():
if not check_password_hash(password_hash, request.form.get('password', '')):
abort(401)
session['user_id'] = 7
return {'ok': True}
@app.route('/notes', methods=['GET', 'POST'])
def notes():
owner = session.get('user_id')
if owner is None:
abort(401)
if request.method == 'GET':
rows = db.session.scalars(select(Note).where(Note.owner == owner).order_by(Note.id))
return {'titles': [row.title for row in rows]}
value = request.form.get('title', '').strip()
if not value or len(value) > 20:
return {'error': 'title invalid'}, 400
db.session.add(Note(owner=owner, title=value))
db.session.commit()
return {'saved': True}, 201
with app.app_context():
db.create_all()
db.session.add(Note(owner=8, title='Private'))
db.session.commit()
client = app.test_client()
token = client.get('/token').json['token']
client.post('/login', data={'password': 'course-password'}, headers={'X-CSRFToken': token})
headers = {'X-CSRFToken': token} if token_mode == 'correct' else {}
response = client.post('/notes', data={'title': title}, headers=headers)
listed = client.get('/notes').json['titles']
return {'status': response.status_code, 'titles': listed}- Reasoning
- The fixture creates an in-memory table and one foreign note, then signs in through a real password-hash check using a session-bound CSRF token. A valid title is trimmed, assigned to the authenticated fixture owner and committed.