DEV Community

Cover image for I built an API client that runs in your browser. Here's how I handled CORS without running an open proxy

I built an API client that runs in your browser. Here's how I handled CORS without running an open proxy

Anirudha Sonwane on September 28, 2026

I built a free API client that runs in the browser as part of Tool Reign. It isn't open source, so there's no code in this post. But the design dec...
Collapse
 
omyvnss profile image
Om Yaduvanshi •

good stuff, anirudha. this is an unusually honest build post, the part about a proxy seeing tokens is the bit most people would bury in a footnote, and you gave it a section heading instead.

the per-hop redirect check before carrying Authorization forward is the detail i was waiting for. most proxy writeups hand-wave redirects entirely.

direct-first with proxy as opt-in is the right default. one real-world snag: store-nothing logs make debugging proxy failures harder in practice. fine tradeoff, just a real one.

Collapse
 
anirudha_sonwane_ca3fc720 profile image
Anirudha Sonwane •

Thanks Om. Yes, it's a real trade-off. Logging only a request ID, status class, error code and duration tells me a call failed but not why. I just hit that myself: a network error that looked identical for several different causes.

I'm fixing it by moving the diagnosis to the client: structured error codes and hints in the response, a health probe, and a "test connection" button. The person debugging gets the reason, and my server still doesn't keep their URLs or tokens.

Collapse
 
omyvnss profile image
Om Yaduvanshi •

nice resolution. the promise stays intact server-side, and the person hitting the failure still gets something useful. the test connection button is the part i would have asked for next. beats debugging over dms.

one thing to watch: keep the hints categorical in proxy mode. a dns-failure hint that includes the resolved address would hand the server something it promised not to know, which undoes the whole point of store-nothing.

Thread Thread
 
anirudha_sonwane_ca3fc720 profile image
Anirudha Sonwane •

Thanks for the follow-up, Om. You were right about the race. The server classified DNS failures correctly, but the response wasn't reaching the client. It was a status-code issue at the edge, not a timer. It's fixed now: DNS failures return a categorical DNS_FAILED code with no hostname or resolved address in the response, and logs record only a request ID, status class, error code and duration. I verified it with 10 live requests.

Thread Thread
 
omyvnss profile image
Om Yaduvanshi •

clean fix. categorical code plus no hostname leaking back to the client is the right pair. easy to get one right and forget the other.

the 10 live requests bit is what i want to ask about. how did you trigger the dns failures, real dead hosts or something synthetic? edge status-code bugs are the ones that hide from synthetic tests the longest.

Thread Thread
 
anirudha_sonwane_ca3fc720 profile image
Anirudha Sonwane •

Synthetic, honestly. I sent 10 requests to a made-up host under the reserved .invalid TLD through the live endpoint, so they went through Cloudflare and the real server, which is how the edge rewriting 5xx bodies showed up. I haven't tested real-world cases yet: dead nameservers, SERVFAIL, or slow resolvers. The new 4s DNS timeout is only covered by a unit test with a lookup that never resolves. Do you have a favourite way to simulate a slow or dead resolver that doesn't need a domain I control?

Collapse
 
szp2005 profile image
szp2005 •

About the per-client rate limits: if you ever add IP reputation to catch people rotating proxies, don't trust one feed. Some flag whole AWS or GCP ranges as proxies, so a dev calling from a cloud VM looks like an abuser. I only count a datacenter IP as a proxy when two dedicated sources agree. For residential IPs one is enough.

Collapse
 
anirudha_sonwane_ca3fc720 profile image
Anirudha Sonwane •

Good catch, and useful to have on record before I'd build that. Right
now rate limiting is just per-IP request counts, no reputation lookup.
If I ever add one, requiring two independent sources to agree on
"datacenter = proxy" (one for residential) is exactly the kind of
detail I'd get wrong on the first pass. Appreciate you flagging it
now rather than after I ship it.

Collapse
 
henry786 profile image
Henry •

Handling CORS like this is super clean. Over at The Printing World, we often deal with rigid, legacy vendor APIs for tracking materials, so having a reliable proxy logic setup like this would save our tech guys a ton of integration headaches.

Collapse
 
anirudha_sonwane_ca3fc720 profile image
Anirudha Sonwane •

Thanks Henry! Legacy vendor APIs are exactly where this pattern pays off, since you can't change their CORS headers but you can control the hop in front of them. The parts I'd keep if you build something similar are pinning the resolved IP to block DNS rebinding, refusing to follow redirects, and logging only metadata, never URLs or bodies. Are those vendor APIs mostly called from browser clients, or from your own backend?

Collapse
 
unitbuilds profile image
UnitBuilds •

Do not follow external links, this is a phishing scam. DEV.to uses Sloan for automated messaging.