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 &lt;, printing it as text instead of executing it.

Python
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.

Python
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!

Python
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}), 201

4. 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.

Python
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()}!"}), 200
PythonRuns entirely in your browser — nothing is sent to a server.

Practice Questions

Question 1

Which Flask decorator modifies a Python function so it is triggered when a specific URL is visited?

  • @app.route('/path')
  • @flask.url('/path')
  • @app.serve('/path')
  • @url_handler('/path')

Question 2

In a Flask Trade Order route handling both GET and POST requests, how do you retrieve data submitted via an HTML form?

  • request.json.get('qty')
  • request.args.get('qty')
  • request.form.get('qty')
  • request.body.get('qty')

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?

  • POST for data, GET for trades
  • GET for data, POST for trades
  • PUT for data, DELETE for trades
  • FETCH for data, PUSH for trades

Question 4

Why do production REST APIs require Token-Based Authentication rather than relying solely on the client's IP address?

  • IP addresses are permanent and globally unique to specific bots, making them safe but too slow to verify.
  • IP addresses can easily be spoofed, change dynamically, and multiple bots or users might share the same office IP.
  • Token authentication doesn't require verifying the user's identity on the server side.
  • Tokens automatically encrypt the entire web request, whereas IP routing does not.