Skip to main content

Overview

APIs are software intermediaries that allow applications to communicate with each other. Building a secure API requires implementing multiple layers of protection including rate limiting, input validation, CORS configuration, and proper error handling.
A production API requires additional security measures beyond this guide including authentication, HTTPS encryption, content security policies, and comprehensive logging.

Setting Up a Secure Flask API

Installation Requirements

Install the necessary security packages:
pip install flask_cors

Basic API Structure

Here’s a secure Flask API implementation with rate limiting and CORS protection:
main.py
from flask import Flask
from flask import request
from flask import jsonify
import database_management as dbHandler
from flask_cors import CORS, cross_origin
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address


api = Flask(__name__)
cors = CORS(api)
api.config["CORS_HEADERS"] = "Content-Type"
limiter = Limiter(
    get_remote_address,
    app=api,
    default_limits=["200 per day", "50 per hour"],
    storage_uri="memory://",
)


@api.route("/", methods=["GET"])
@limiter.limit("1/second", override_defaults=False)
def get_film():
    film = dbHandler.get_random_film()
    # For security data is validated on entry
    if request.args.get("like") and request.args.get("like").isdigit():
        film_id = request.args.get("like")
        api.logger.critical(
            f"You have liked the film id={film_id}"
        )  # debugging statement only
        dbHandler.record_like(film_id)
    # For security data is validated on entry
    if request.args.get("dislike") and request.args.get("dislike").isdigit():
        film_id = request.args.get("dislike")
        api.logger.critical(
            f"You have disliked the film id={film_id}"
        )  # debugging statement only
        dbHandler.record_dislike(film_id)
    return jsonify(film), 200


@api.route("/add_film", methods=["POST", "HEAD"])
@limiter.limit("1/second", override_defaults=False)
def add_film():
    data = request.get_json()
    info = dict(request.headers)
    api.logger.critical(f"User {info}")
    api.logger.critical(f"Has added the movie {data}")
    dbHandler.add_film(data)
    return data, 201


if __name__ == "__main__":
    api.run(debug=True, host="0.0.0.0", port=3000)

Security Features Explained

Rate Limiting

Rate limiting protects your API from DoS attacks and excessive demand:
limiter = Limiter(
    get_remote_address,
    app=api,
    default_limits=["200 per day", "50 per hour"],
    storage_uri="memory://",
)

@api.route("/", methods=["GET"])
@limiter.limit("1/second", override_defaults=False)
def get_film():
    # Route handler code
Rate limiting is essential for public APIs to prevent abuse and maintain availability for legitimate users.

CORS Configuration

Cross-Origin Resource Sharing (CORS) allows your API to be accessed by other domains:
cors = CORS(api)
api.config["CORS_HEADERS"] = "Content-Type"
For production APIs, configure CORS to allow only specific trusted domains instead of allowing all origins.

Input Validation

Validate all input data before processing:
# Validate that the parameter is a digit before processing
if request.args.get("like") and request.args.get("like").isdigit():
    film_id = request.args.get("like")
    dbHandler.record_like(film_id)

Database Management

Implement secure database operations in a separate module:
database_management.py
from flask import jsonify


def get_random_film():
    # driver response only, to be implemented
    return {"id": 1, "name": "Frozen", "studio": "Disney"}


def record_like(film_id):
    # to be implemented
    return


def record_dislike(film_id):
    # to be implemented
    return


def add_film(data):
    # verify and sanitise JSON to be implemented
    # Add film to database to be implemented
    return
1

Validate Input

Always validate and sanitize JSON data before adding to the database
2

Use Parameterized Queries

Never construct SQL queries using string concatenation. Use parameterized queries:
cur.execute('SELECT * FROM users WHERE username == ? AND password == ?', (username, password))
3

Implement Error Handling

Handle database errors gracefully without exposing sensitive information

API Endpoints

API CallResult
http://127.0.0.1:3000/Returns a random movie from the database as JSON with response code 200
http://127.0.0.1:3000/?like=123Records a like for film_id “123” if the id exists, returns response code 200
http://127.0.0.1:3000/?dislike=456Records a dislike for film_id “456” if the id exists, returns response code 200
http://127.0.0.1:3000/add_filmAdds a film entry to the database if JSON is valid, returns response code 201

Production Security Requirements

This example is for development environments. Production APIs require:
1

Authentication & Authorization

Implement user authentication and role-based access control
2

HTTPS Encryption

Use HTTPS for all API communication to encrypt data in transit
3

Content Security Policy

Enforce HTTPS communication through CSP headers
4

Comprehensive Logging

Log all HEAD, POST, and GET requests for security analysis
5

Defensive Data Handling

Implement input validation, data sanitization, and exception handling

Testing Your API

curl -X GET http://127.0.0.1:3000/

Testing Tools

  • Thunder Client: VSCode extension for API testing
  • Postman: Standalone application for comprehensive API testing
  • Browser: Use the included index.html file for simple GET/POST/HEAD testing

Best Practices

Key Takeaways:
  • Always validate input data on entry
  • Use rate limiting to prevent abuse
  • Configure CORS appropriately for your use case
  • Log security-relevant events
  • Never expose sensitive data in error messages
  • Use parameterized queries to prevent SQL injection

Additional Resources

Build docs developers (and LLMs) love