DEV Community

Cover image for Building a Production-Ready Anonymous Identity & Abuse Protection Layer with FastAPI
Muhammad Usman Awan
Muhammad Usman Awan

Posted on

Building a Production-Ready Anonymous Identity & Abuse Protection Layer with FastAPI

Most AI applications rely solely on IP-based rate limiting to prevent abuse. Unfortunately, that's only the first line of defense.

In this article, we'll build a production-inspired anonymous identity system using FastAPI, MongoDB, and Beanie that tracks visitors using fingerprints, enforces daily usage limits, prepares for future authentication, and lays the foundation for a scalable AI SaaS.

We'll also see how this integrates cleanly with a multi-provider LLM Router without polluting business logic.

πŸ‘‹ Introduction

When building AI products, most developers focus on prompts, LLMs, and fancy frontend interfaces.

Very few spend time thinking about what happens before the AI is even called.

That request has to be identified.
Validated.
Protected.
Tracked.

And eventually analyzed.

While working on CVForbes, an AI-powered resume tailoring platform, I realized that relying only on IP-based rate limiting wasn't enough.

I wanted something that could:

  • Track anonymous visitors
  • Limit free usage
  • Detect abusive clients
  • Prepare for authentication later
  • Keep the API routes completely clean

So instead of sprinkling checks across endpoints, I built a dedicated identity and abuse protection layer.


πŸ€” Why Not Just Use IP Rate Limiting?

Traditional rate limiting looks something like this:

@limiter.limit("3/minute")
Enter fullscreen mode Exit fullscreen mode

That's a good start.

But users can easily bypass it by:

  • Changing networks
  • Using a VPN
  • Switching devices
  • Clearing browser state

For an AI product where every request costs money, that's not enough.

We needed another layer.


πŸ’‘ The Idea

Instead of identifying requests using only an IP address, every request first becomes a Client Identity.

Client Request
        β”‚
        β–Ό
Client Identity
        β”‚
        β”œβ”€β”€ IP Address
        β”œβ”€β”€ User-Agent
        β”œβ”€β”€ Cookie
        └── Fingerprint
Enter fullscreen mode Exit fullscreen mode

That fingerprint becomes the user's anonymous identity throughout the application.


πŸ— Architecture

                Client Request
                      β”‚
                      β–Ό
              IP Guard Middleware
                      β”‚
                      β”œβ”€β”€ Resolve Client Identity
                      β”œβ”€β”€ Check Active Ban
                      β”œβ”€β”€ Get/Create Anonymous User
                      β”œβ”€β”€ Reset Daily Usage
                      β”œβ”€β”€ Validate Daily Limit
                      └── Attach User to request.state
                      β”‚
                      β–Ό
                  API Endpoint
                      β”‚
                      β–Ό
            CV Generation Service
                      β”‚
                      β–Ό
                  LLM Router
      (Automatic Failover Between Providers)
                      β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β–Ό             β–Ό             β–Ό            β–Ό
      Groq      OpenRouter      Ollama      Gemini
        β”‚             β”‚             β”‚            β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
                      β–Ό
        Structured Output Validation
                      β”‚
                      β–Ό
          Tailored CV + Cover Letter
Enter fullscreen mode Exit fullscreen mode

Notice something?

The route knows absolutely nothing about users.

The middleware handles everything before the request reaches business logic.


πŸ“‚ Organizing the Backend

Instead of putting everything inside routes, I split responsibilities.

app/

β”œβ”€β”€ middleware/
β”‚      └── ip_guard.py
β”‚
β”œβ”€β”€ services/
β”‚      β”œβ”€β”€ client_identity_service.py
β”‚      β”œβ”€β”€ anonymous_user_service.py
β”‚      └── abuse_service.py
β”‚
β”œβ”€β”€ repositories/
β”‚      β”œβ”€β”€ anonymous_user_repository.py
β”‚      └── banned_ip_repository.py
β”‚
β”œβ”€β”€ models/
β”‚      β”œβ”€β”€ anonymous_user.py
β”‚      β”œβ”€β”€ banned_ip.py
β”‚      └── generated_cv.py
Enter fullscreen mode Exit fullscreen mode

Each layer has only one responsibility.


πŸ—„ Designing the Anonymous User

Instead of storing only an IP address, each anonymous visitor stores useful analytics.

class AnonymousUser(Document):
    fingerprint: str
    ip: str
    user_agent: str
    cookie_id: str

    requests_today: int = 0
    total_requests: int = 0

    abuse_score: int = 0
    is_banned: bool = False
Enter fullscreen mode Exit fullscreen mode

Now we know:

  • who visited
  • how often
  • when they visited
  • whether they were abusive

without requiring sign-up.


🧠 Services Instead of Fat Routes

The route doesn't increment counters anymore.

Instead:

await anonymous_service.register_request(user)
Enter fullscreen mode Exit fullscreen mode

Behind that single line:

  • reset daily limit
  • validate usage
  • update timestamps
  • increment counters
  • save to MongoDB

The endpoint remains tiny.


πŸ›‘ Middleware Does the Heavy Lifting

The middleware performs the entire security pipeline.

identity = identity_service.resolve(request)

user = await anonymous_service.get_or_create(identity)

await anonymous_service.register_request(user)

request.state.user = user
Enter fullscreen mode Exit fullscreen mode

Now every endpoint automatically receives a validated anonymous user.

No duplicated logic.


🚫 Preparing for Bans

Another service handles abuse.

if await abuse_service.is_banned(identity.fingerprint):
    raise HTTPException(
        status_code=403,
        detail="Access denied."
    )
Enter fullscreen mode Exit fullscreen mode

Later this can become:

  • manual admin bans
  • automatic bans
  • permanent bans
  • temporary bans
  • abuse score thresholds

without changing middleware.


🀝 Integrating with the LLM Router

One of my favorite parts is that the protection layer has nothing to do with AI providers.

Whether the router selects:

  • Groq
  • OpenRouter
  • Ollama
  • Gemini

the security layer behaves exactly the same.
That separation keeps the architecture flexible.


πŸ“ˆ Current Flow

A request now follows this path:

Request
    ↓
Identity
    ↓
Anonymous User
    ↓
Daily Limit
    ↓
Ban Check
    ↓
API
    ↓
CV Service
    ↓
LLM Router
    ↓
Structured Validation
    ↓
Response
Enter fullscreen mode Exit fullscreen mode

Simple.

Predictable.

Easy to extend.


🎯 Final Thoughts

Building an AI application isn't only about prompts or choosing the fastest LLM. The real engineering challenge is designing a backend that remains maintainable as the product grows.

By introducing an anonymous identity layer, separating responsibilities into middleware, services, and repositories, and keeping the LLM Router completely independent, CVForbes now has a foundation that's easy to extend with authentication, subscriptions, analytics, and future AI providers.

Sometimes the best feature isn't the one users seeβ€”it's the architecture that quietly keeps everything secure, scalable, and understandable behind the scenes.

Did you miss my deep dive into provider-agnostic AI architecture? You can find it right here:
How I built a provider-agnostic AI architecture that automatically switches between Groq, OpenRouter, Ollama, and Gemini.

Top comments (0)