Building Secure REST APIs with Python, Flask and Firebase
Introduction
Building a REST API that is secure, scalable, and maintainable requires more than just getting the endpoints to return data. In this post I'll walk through the exact architecture I use in production — Flask blueprints, Firebase Firestore as the database, JWT authentication, and Celery for async tasks.
Project Structure
The foundation of a good Flask API is the Application Factory pattern. Instead of creating the app at module level, you wrap it in a function:
from flask import Flask
from flask_cors import CORS
def create_app():
app = Flask(__name__)
CORS(app, origins=["https://yourdomain.com"])
from .public_api import public_bp
from .admin_api import admin_bp
app.register_blueprint(public_bp, url_prefix="/api")
app.register_blueprint(admin_bp, url_prefix="/admin/api")
return app
This pattern makes testing trivial and keeps your config isolated.
Authentication with JWT
Never use session-based auth for a REST API. Use short-lived access tokens paired with long-lived refresh tokens:
- Access token — 15 minutes. Sent in every request header
- Refresh token — 30 days. Used only to get a new access token
- Blacklist — stored in Firestore. Tokens are invalidated on logout
from flask_jwt_extended import create_access_token, create_refresh_token
def login():
# Verify credentials...
access_token = create_access_token(identity=admin_id)
refresh_token = create_refresh_token(identity=admin_id)
return jsonify(
access_token=access_token,
refresh_token=refresh_token
)
Firestore as Your Database
Firebase Firestore is a great fit for portfolio APIs — no SQL schema to manage, scales automatically, and the Python SDK is straightforward:
from google.cloud import firestore
db = firestore.Client()
def get_posts(status="published"):
docs = (
db.collection("blog_posts")
.where(filter=FieldFilter("status", "==", status))
.order_by("created_at", direction=firestore.Query.DESCENDING)
.stream()
)
return [{"id": d.id, **d.to_dict()} for d in docs]
Note the FieldFilter keyword syntax — required in newer versions of the Firestore SDK.
Async Email with Celery
Never send emails synchronously in a request handler. The user should never wait for an email to be sent. Always queue it:
from celery import Celery
celery = Celery(__name__, broker="redis://localhost:6379/0")
@celery.task(queue="contact")
def send_contact_email(data):
# send email here — runs in background worker
pass
# In your route:
@contact_bp.route("/contact", methods=["POST"])
def contact():
data = request.get_json()
send_contact_email.delay(data) # non-blocking
return jsonify(message="Message received"), 202
The 202 Accepted status code is the correct response — it means "I received your request and it is being processed."
Deployment
The production stack for this API:
| Layer | Technology |
|---|---|
| WSGI Server | Gunicorn (3 workers) |
| Reverse Proxy | Nginx |
| SSL | Let's Encrypt via Certbot |
| Process Manager | systemd |
| Task Queue | Celery + Redis |
| Database | Firebase Firestore |
Conclusion
This architecture has served me well across multiple production projects. The key principles are separation of concerns via blueprints, never blocking the request thread with slow operations, and keeping authentication stateless with JWT.
If you have questions or want to see a specific part expanded, reach out via the contact form.
Comments