Web Development & APIs with Flask
Build Banking Dashboards & Trading APIs
Welcome to Chapter 8! Python isn't just for data analysis—it's the powerhouse behind many modern web applications and APIs. In this chapter, we're diving into Flask, a lightweight but incredibly powerful web framework.
Through the lens of Fintech, you will learn how to route web traffic to a Banking Dashboard, process Trade Order forms, design JSON-based Algorithmic Trading APIs, and secure them against unauthorized access using Token Authentication.
1. Flask Basics: Banking Dashboard & XSS
Flask operates under the WSGI standard, turning Python functions into web endpoints. In a financial context, you might build a Banking Dashboard using Jinja2 templates to dynamically render portfolio balances.
Security Feature: XSS Prevention (Cross-Site Scripting)
What if a malicious actor names their transaction <script>stealCookies()</script>? If rendered directly into HTML, the browser executes the script! Fortunately, Flask's Jinja2 engine Auto-escapes variables by default. It safely converts dangerous characters like < into <, printing it as text instead of executing it.
from flask import Flask, render_template_string app = Flask(__name__) HTML_TEMPLATE = """<!DOCTYPE html><html><body> <h1>Welcome back, {{ name }}</h1> <p><strong>Account Balance:</strong> ${{ balance }}</p> <h3>Recent Transactions (XSS Protected)</h3> <ul> {% for tx in transactions %} <!-- EXPLANATION: Jinja2 automatically escapes the {{ tx.desc }} variable. --> <!-- A malicious script will be rendered harmlessly as plain text. --> <li>{{ tx.date }} - {{ tx.desc }}: ${{ tx.amt }}</li> {% endfor %} </ul></body></html>""" @app.route('/dashboard')def dashboard(): client_data = { "name": "Jane Doe", "balance": 15420.50, "transactions": [ {"date": "2024-03-01", "desc": "AAPL Dividend", "amt": 150.00}, # EXPLANATION: A simulated XSS attack! Jinja2 will disarm this automatically. {"date": "2024-03-02", "desc": "<script>alert('XSS Hack Attempted!')</script>", "amt": -305.20} ] } return render_template_string(HTML_TEMPLATE, **client_data)2. Flask Forms & Requests: CSRF Protection
Web applications accept data via HTTP POST requests. However, this opens you up to CSRF (Cross-Site Request Forgery). An attacker could host a hidden form on evil-site.com that submits a real trade to your bank using the victim's saved session cookies!
Security Feature: CSRF Tokens
To prevent this, every form must include a hidden, cryptographically random string generated by the server. When the form is submitted, the server verifies this string. Since evil-site.com cannot read your site's token, the forgery fails.
In Flask, we use Flask-WTF to handle this gracefully via the CSRFProtect extension.
from flask import Flask, request, jsonify, render_template_stringfrom flask_wtf.csrf import CSRFProtect app = Flask(__name__)# EXPLANATION: Secret key is required to cryptographically sign the CSRF tokensapp.config['SECRET_KEY'] = 'super-secret-key-123' # EXPLANATION: Initialize CSRF Protection globally for the app.# This ensures that ALL POST requests require a valid token.csrf = CSRFProtect(app) PRICES = {"AAPL": 172.50} FORM_HTML = """<form method="POST" action="/submit-trade"> <!-- EXPLANATION: Inject the hidden CSRF token into the HTML form. --> <!-- Without this token, the server will outright reject the submission. --> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="text" name="ticker" value="AAPL" /> <input type="number" name="qty" value="10" /> <button type="submit">Buy</button></form>""" @app.route('/trade', methods=['GET'])def trade_form(): return render_template_string(FORM_HTML) @app.route('/submit-trade', methods=['POST'])def submit_trade(): # EXPLANATION: CSRFProtect automatically checks the token BEFORE this function runs! # If the token is missing or invalid, it throws a 400 Bad Request Error. ticker = request.form.get('ticker', '').upper() qty = int(request.form.get('qty', 0)) if ticker not in PRICES or qty <= 0: return "Invalid Trade Details", 400 notional = round(PRICES[ticker] * qty, 2) return f"Success! Order Placed for ${notional} estimated."3. REST API Design: Rate Limiting
A REST API allows machines to communicate using JSON. However, a broken algorithmic bot could send 10,000 trade execution requests per second, taking your server completely offline (a DDoS attack) or draining an account instantly!
Security Feature: Rate Limiting
To avoid abuse, production APIs strictly enforce Rate Limits (e.g., 5 requests per second per IP). If a bot goes over the limit, the server immediately returns a 429 Too Many Requests HTTP status code without processing the heavy logic.
While large applications use Redis-backed libraries like Flask-Limiter, you can build a robust custom Rate Limiting decorator natively in Python to understand how it works!
from flask import Flask, jsonify, requestimport timefrom functools import wraps app = Flask(__name__)portfolio_trades = [] # EXPLANATION: A simple dictionary to track request timestamps per IP address.# In production, this would be a high-speed Redis cluster.request_history = {} def rate_limit(max_requests=5, period=10): """EXPLANATION: Custom decorator: Allows max_requests per specified period (seconds).""" def decorator(f): @wraps(f) def wrapped(*args, **kwargs): ip = request.remote_addr or '127.0.0.1' now = time.time() # 1. Look up the IP and filter out timestamps older than our 'period' history = request_history.get(ip, []) history = [ts for ts in history if now - ts < period] # 2. If they have made too many requests recently, block them immediately! if len(history) >= max_requests: return jsonify({"error": "429 Too Many Requests: Slow down!"}), 429 # 3. Otherwise, record this request and allow them through history.append(now) request_history[ip] = history return f(*args, **kwargs) return wrapped return decorator @app.route('/api/v1/execute-trade', methods=['POST'])@rate_limit(max_requests=2, period=5) # EXPLANATION: Limit to 2 trades every 5 seconds!def execute_trade(): """Bot posts JSON to this endpoint to execute a trade.""" data = request.json trade = {"ticker": data.get('ticker'), "qty": data.get('qty')} portfolio_trades.append(trade) return jsonify({"status": "executed", "trade": trade}), 2014. API Authentication & Input Sanitization
Modern APIs use Token-based Authentication. The native itsdangerous library is perfect for creating secure timed tokens natively.
Security Feature: Input Sanitization (SQL Injection)
If an API does not sanitize inputs, a bot might submit a ticker named '); DROP TABLE trades;--. If you concatenate this blindly into raw SQL, your database goes down permanently. Never use raw Python string formatting like f"SELECT * FROM trades WHERE ticker='{ticker}'". Always use an ORM like SQLAlchemy, or strictly parameterize your raw queries.
from flask import Flask, request, jsonifyfrom itsdangerous import URLSafeTimedSerializer, BadSignatureimport os app = Flask(__name__)# EXPLANATION: Security Best Practice: Never hardcode secrets. # Pull them from environment variables securely so they never leak in Git.app.config['SECRET_KEY'] = os.environ.get('API_SECRET_KEY', 'default-dev-key')serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) def is_valid_ticker(ticker): """EXPLANATION: Sanitize and validate input to protect against injection/anomalies""" if not isinstance(ticker, str) or not ticker.isalpha(): return False # Blocks symbols like ', (, ), ; and SQL commands return len(ticker) <= 5 # Tickers shouldn't be long strings @app.route('/api/protected-trade', methods=['POST'])def protected_trade(): # EXPLANATION: Extract the Token from the Authorization Header auth_header = request.headers.get("Authorization") if not auth_header or not auth_header.startswith("Bearer "): return jsonify({"error": "Missing Authorization header"}), 401 try: # EXPLANATION: Cryptographically load the token. Will fail if tampered with or expired! data = serializer.loads(auth_header.split(" ")[1], max_age=60) except BadSignature: return jsonify({"error": "Invalid token"}), 401 ticker = request.json.get('ticker', '') # EXPLANATION: Security: Strict Input Sanitization before touching any business logic or SQL if not is_valid_ticker(ticker): return jsonify({"error": "Malicious or invalid Ticker format rejected"}), 400 # Passed Auth and Sanitization Validation! return jsonify({"message": f"Secured Trade authorized for {data['bot_id']} on {ticker.upper()}!"}), 200Practice Questions
Question 1
Which Flask decorator modifies a Python function so it is triggered when a specific URL is visited?
Question 2
In a Flask Trade Order route handling both GET and POST requests, how do you retrieve data submitted via an HTML form?
Question 3
When building a RESTful Algorithmic Trading API, which HTTP verbs are conventionally used for retrieving market data and placing a new trade order, respectively?
Question 4
Why do production REST APIs require Token-Based Authentication rather than relying solely on the client's IP address?