DEV Community

Ume
Ume

Posted on

Designing “Just Enough” API Security for Solo Developers

How I Avoided Overengineering While Keeping My API Safe

API security in solo development is tricky.

It’s often unclear how far is “enough”,

which leads to either:

  • overengineering everything out of fear, or
  • leaving critical parts completely unprotected.

In my own projects, I evaluated both Firebase’s simplicity and AWS’s robustness,

but repeatedly ran into the same problems:

  • cost
  • operational complexity
  • long-term maintenance burden

(Related article: The Obstacles I Faced in Personal Development and How I Chose My Tech Stack)

What I eventually settled on was this idea:

Don’t aim for perfection.

Protect your API in layers.

This article summarizes what I decided to protect,

what I intentionally did not,

and why—based on actually building a small API as a solo developer.


Target Architecture

  • React Native (client)
  • Cloudflare Workers + Hono (API)
  • Supabase (Auth / PostgreSQL / RLS)

The API is designed to be called from multiple client applications.

Demo repository:

https://github.com/umemura-dev/hono-supabase-auth-security-demo


1. Security Was Not Clearly Defined at the Start

From the beginning, I knew security mattered.

But I didn’t know how strong it needed to be.

Once you start thinking about security,

the list of possible attacks never ends.

I found myself stuck in a loop of anxiety:

“What if this attack happens?”

“What about that scenario?”

As a result, it was difficult to move forward.


2. Deciding the Scope Before Writing Code

I realized that security design needs boundaries.

Before implementing anything, I decided to define:

what I would consider

and

what I would intentionally ignore


3. My Practical Threat Model

This was not a formal threat model.

It evolved while building the API.

What I assumed would happen

  • Unauthenticated requests
  • Invalid or malformed parameters
  • Requests with stolen or replayed JWTs

What I chose not to assume

  • Perfect secrecy of client-side code
  • Defenses against full reverse engineering of native apps
  • Stopping every possible attack at the API layer alone

As a solo developer,

I limited the scope to what I could realistically manage.


4. Moving Away from “The API Must Do Everything”

Initially, I thought:

“The API should validate everything perfectly.”

But during implementation, I realized:

  • bugs happen
  • edge cases are missed
  • future changes can weaken assumptions

So I changed my mindset:

Don’t rely on a single layer to be perfect.


5. Splitting Responsibilities by Layer

I ended up with the following responsibility split.

API layer (Cloudflare Workers)

  • Basic request validation at the entry point
  • JWT verification and user identification
  • Rejecting unexpected clients
  • Input validation
  • Lightweight rate limiting

Supabase Auth

  • Guaranteeing authenticated users
  • Providing consistent user IDs (UIDs)

Supabase RLS

  • Row-level access control
  • Protecting data even if the API layer makes mistakes

No single layer is trusted completely.


6. Why JWT Alone Was Not Enough

JWT tells you:

  • who the user is
  • whether they are authenticated

But it does not tell you:

  • which application sent the request
  • whether the client was expected

That led to this conclusion:

users and applications should be verified separately

So I added application-level verification at the API entry point.


7. Keeping Request Signing “Reasonable”

For application identification,

I use request signing (HMAC).

However, I intentionally avoided:

  • signing the request body
  • strict nonce management
  • perfect replay protection

Instead, I sign only:

  • timestamp
  • HTTP method
  • path

This was a conscious tradeoff between:

  • implementation cost
  • operational complexity
  • actual threat level

Also, since the secret key lives in the client (React Native),

I did not consider full HMAC-level rigor realistic for solo development.

(Related article:Protecting the API Entry Point with Cloudflare Workers)


8. RLS as the Final Line of Defense

No matter how careful the API design is,

the following will eventually happen:

  • bugs
  • refactoring mistakes
  • new routes added later

So I made one rule non-negotiable:

even if the API is bypassed, other users’ data must not be accessible

Supabase RLS enforces this at the database level.

(Related article:How I Designed Supabase and Row Level Security (RLS))


9. Final Architecture (Conceptual View)

Layer 1: Client
[ React Native App ]

  • JWT + Signature

Layer 2: API
[ Cloudflare Workers API ]

  • app validation
  • JWT verification

Layer 3: Database
[ Supabase PostgreSQL ]

  • RLS enforcement

Result:
[ Authorized JSON Response ]

Even if one layer fails,

the next layer is expected to stop the request.

This layered thinking made it much easier to decide

how far security design should go.


10. Logging and Error Design

Security is not only about prevention.

It’s also about being able to investigate incidents.

In this demo API:

  • every request is assigned a requestId
  • logs are structured JSON for future aggregation
  • error responses to clients are minimal
  • internal logs clearly distinguish auth and authorization failures

At the moment, logs go to console,

but the design allows production-grade logging by simply swapping outputs.


11. Final Thoughts

While building this demo API, I learned that:

designing security “correctly” from day one is extremely difficult

What helped was changing my approach:

don’t design everything at once—decide the order.

The order that worked best for me:

  1. Fix assumptions

    • what is the client
    • who are the users
  2. Decide threat boundaries

    • what must be protected
    • what is acceptable to ignore
  3. Lock down the database

    • RLS to prevent cross-user access
  4. Confirm user identity

    • JWT authentication
  5. Validate requests at the API entry

    • basic validation
    • reject unexpected traffic
  6. Add application-level identification if needed

    • signatures / timestamps
  7. Throttle unintended request bursts

    • rate limiting
  8. Make issues traceable

    • requestId
    • structured logs

By following this sequence,

I arrived at an API design that is:

not overengineered,

but difficult to break accidentally.

I hope this article helps you decide

how far your own API security should go.

Top comments (0)