All Posts
Building Secure REST APIs with Python, Flask and Firebase

Building Secure REST APIs with Python, Flask and Firebase

A practical walkthrough of architecting production-grade REST APIs using Flask blueprints, Firebase Firestore, JWT authentication and Celery task queues — with real code examples.

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:

python
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

python
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:

python
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:

python
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:

LayerTechnology
WSGI ServerGunicorn (3 workers)
Reverse ProxyNginx
SSLLet's Encrypt via Certbot
Process Managersystemd
Task QueueCelery + Redis
DatabaseFirebase 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.

Enjoyed this post?

Get notified when I publish next.

No spam — only new posts on networking, security, DevOps and infrastructure.

Comments

Leave a comment