DEV Community

Ume
Ume

Posted on

Protecting the API Entry Point with Cloudflare Workers

Verifying Application Authenticity Beyond JWT

In this article, I describe how I designed an API that assumes multiple client applications, using the following architecture:

React Native → Cloudflare Workers (Hono) → Supabase

A demo implementation is available here:

GitHub:

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

In the previous article, I explained how Supabase Auth and RLS guarantee

who can access which data at the database layer.

This article focuses on one step earlier:

how incoming requests themselves are handled at the API entry point.


1. Why I Felt JWT Alone Was Not Enough

With Supabase Auth, JWTs allow us to verify:

“Which user is making this request?”

However, as I refined the design, several concerns emerged:

  • JWT proves user identity
  • It does not prove which application sent the request
  • If a token is leaked, requests can still be made as a valid user
  • Bots, scripts, and unintended clients are indistinguishable

In other words:

user authenticity and application authenticity are separate concerns.

JWT is essential, but relying on it alone felt insufficient.


2. Introducing “Application Authentication”

To address this, I separated responsibilities as follows:

  • JWT: who the user is
  • App Guard: which application is making the request

In addition to user authentication, the API verifies:

“Is this request coming from a pre-approved application?”

This mechanism is referred to here as App Guard.


3. App Guard Specification in the Demo

App Guard uses the following request headers:

  • X-App-Id
  • X-App-Timestamp
  • X-App-Signature

Each serves a distinct purpose.

X-App-Id

Identifies which client application is sending the request.

The server maintains pre-registered configurations per app.

X-App-Timestamp

Indicates when the request was created.

This helps prevent replay attacks using old requests.

X-App-Signature

Verifies that the request was not tampered with in transit.


4. The Concept of Request Signing

Each client application shares a secret key with the server.

When sending a request:

  1. The app computes a value from the request content and the secret key
  2. The value is sent as X-App-Signature
  3. The server performs the same calculation
  4. If both values match, the request is considered legitimate

This ensures:

  • Third parties cannot forge valid requests without the secret
  • Any request modification breaks the signature

Internally, this uses a standard signing algorithm (HMAC),

but this article focuses on the design rationale, not the cryptographic details.


5. Why This Signing Format Was Chosen

The signature input is constructed as:

${timestamp}.${METHOD}.${path}
Enter fullscreen mode Exit fullscreen mode

This format was chosen deliberately.

Why include the timestamp

  • Prevents replaying old requests
  • Makes requests valid only for a short time window

Why include METHOD and path

  • Prevents confusion between GET and POST
  • Prevents reuse against different endpoints

Why the request body is excluded

  • JSON ordering and formatting differences easily break signatures
  • Increases implementation and operational complexity
  • Not cost-effective for a small-scale API

Instead of perfection,

a balance between security and maintainability was prioritized.


6. Why STRICT and LENIENT Modes Exist

App Guard supports multiple validation levels:

  • STRICT: timestamp and signature required
  • LENIENT: timestamp only
  • NONE: validation skipped (development use)

Applying maximum strictness everywhere can be overkill.

This flexibility allows each application to choose an appropriate security level without excessive overhead.


7. Implementation Approach with Cloudflare Workers (Hono)

App Guard is implemented as a Hono middleware:

  • Runs before all routes
  • Immediately returns 401 / 403 on failure
  • Logs App-Id and failure reasons

This design allows:

  • API handlers to focus purely on business logic
  • Security logic to be centralized and consistent

8. Responsibility Split: App Guard / JWT / RLS

Security in this API is layered, not monolithic.

Layer Responsibility
App Guard Is this a legitimate application?
JWT Is this a legitimate user?
RLS Can this user access this data?

Even if one layer is compromised,

the next layer is expected to stop the request.


9. Important Limitations

This App Guard design assumes the client-side secret key remains protected.

If a client application is reverse-engineered and the secret leaks,

valid signatures can still be generated.

This means App Guard alone is not a perfect defense.

In this demo API, the weakness is mitigated by combining:

  • Mandatory Supabase Auth (email/password)
  • JWT-based authenticated users
  • RLS enforcing “only your own data” at the database level

As a result:

  • Unauthenticated users cannot access data
  • Authenticated users cannot access others’ data
  • A leaked App Guard secret alone does not lead to data exposure

Rather than relying on any single mechanism,

this design emphasizes defense in depth.


10. Summary

In this demo API:

  • JWT verifies users
  • App Guard verifies applications
  • RLS enforces data access rules

Even small APIs can achieve layered security

with careful responsibility separation.

Cloudflare Workers are particularly well-suited for

controlling and validating requests at the API entry point.

All concepts discussed here are implemented in the following repository:

  • Cloudflare Workers + Hono API
  • Supabase Auth (JWT) + PostgreSQL RLS
  • App Guard (signature-based client authentication)
  • CI passing (Biome / TypeScript / Tests)

GitHub:

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

Top comments (0)