Laying the Foundation: Architecture and the Security-First Proxy
Why MistPulse's mobile app never touches a Mist API token, and the Supabase edge-function relay that stands in its place as the system's only security boundary.
Part 2 of 10 in Building MistPulse: A Network Architect's Field Notes

The single most important architectural decision in MistPulse happened before a single screen was designed: the mobile app would never hold a Mist API token. Not encrypted on-device, not in a keychain, not anywhere the app's own process could read it directly. That decision meant the project needed three layers instead of one: a mobile client, a backend, and a database, and it meant the backend had to earn its keep as a genuine security boundary, not just a convenience layer.
The stack, and why each piece earned its place
MistPulse is a React Native app built on Expo (SDK 54), which gave me a single codebase that runs on iOS with a fast development loop and access to native modules when I needed them, which, as later posts will cover, I very much did. For the backend, I chose Supabase: it bundles a managed Postgres database with authentication and, critically, edge functions: small serverless functions that can sit in front of an external API and enforce rules on every request that passes through.
That edge function is the heart of the whole system. I named it mist-proxy, and its entire job is to be the only thing in the architecture that ever sees a real Mist API token. The mobile app authenticates to Supabase with its own user session. When it needs data from Mist, it calls mist-proxy, which looks up the token associated with that user's organization, attaches it to a request to the real Mist API, and returns the response. The phone asks for "site health for site X." It never asks Mist directly, and it never sees the credential that made the answer possible.
The phone asks for the data. It never sees the credential that made the answer possible.

Why a relay instead of "just encrypt it on the phone"
It's tempting to think encryption solves this: store the token in the iOS Keychain, wrap it with device-level protections, call it a day. I considered that path and rejected it for a simple reason: encryption at rest on a device doesn't protect you from every threat a mobile app faces. A jailbroken device, a debugging session, a supply-chain issue in a dependency, or simply a bug in my own code could all end with that token exposed in memory or in a log. A relay changes the blast radius entirely: if the phone is compromised, an attacker gets access to whatever that user's session allows through the proxy, not a standing, portable credential they can replay from anywhere, forever.
The proxy also gave me a place to enforce something a bare token never can: a path allowlist. Every request that reaches mist-proxy is checked against a regular-expression allowlist of approved Mist API paths before it's forwarded. If a code path, or a bug, or an injected value, tries to reach an endpoint that isn't on the list, the proxy simply refuses. That one control has already saved me from my own mistakes more than once, which is exactly the point of building a security boundary you don't have to trust yourself to remember.
- Mobile app: React Native + Expo (SDK 54), talks only to Supabase
- Backend: Supabase edge function (
mist-proxy), Deno runtime, holds the only copy of the Mist API token - Database: Supabase Postgres, stores org-scoped tokens encrypted at rest (AES-256-GCM) and app metadata
- Security control: regex-based path allowlist inside the proxy, checked on every outbound Mist API call
Encryption at rest, because a database is also a target
Even inside the proxy layer, the Mist API token has to live somewhere between requests, and "somewhere" is a Postgres table. Storing it in plaintext, even behind Supabase's own access controls, felt like leaving a second door unlocked after carefully bolting the first one. So the token is encrypted at rest with AES-256-GCM before it's written to the database, and decrypted only inside the edge function, for the duration of a single outbound call. The encryption key itself lives in Supabase's function environment configuration, never in application code and never in the repository.
This is the kind of decision that adds real complexity for a solo project: key management, rotation planning, a slightly more involved deployment process, and it would have been easy to skip for something "just for my own use." I kept it anyway, because the entire point of this project was proving to myself that convenience and security don't have to trade off against each other if you make the architectural call early enough.
What this foundation made possible
With the proxy in place, everything downstream became simpler to reason about. The mobile app could focus purely on user experience: screens, navigation, offline states, without ever touching credential handling. New Mist API endpoints could be added by extending the allowlist and writing a small handler function, a change that's genuinely low-risk because the security boundary around it doesn't move. And critically, this structure meant I could iterate fast on features without reopening the question of "is this safe to ship," because the safety property was baked into the layer everything else sat on top of.
Architecture on paper is one thing. The next post covers the discipline that made this architecture trustworthy in practice: a rule I adopted early and never broke, that shaped how every single feature in this app got built.
Coming up next: Curl First, Build Second: the API validation methodology that caught more bugs before they existed than any code review ever did
Building MistPulse: A Network Architect's Field Notes
- 1.The Idea: Why I Built MistPulse
- 2.Laying the Foundation: Architecture and the Security-First Proxy (you are here)
- 3.Curl First, Build Second: The API Validation Methodology
- 4.Real Bugs, Real Fixes: Debugging in the Open
- 5.Teaching Siri to Speak Mist
- 6.The Entity Resolution Saga
- 7.Generalizing Features: The Bulk Rename Story
- 8.Security by Design
- 9.When APIs Change Under You: The SLE Migration Story
- 10.Lessons Learned & What's Next
Related posts

Real Bugs, Real Fixes: Debugging in the Open
What breaks when you build a network tool alone, and the recurring bug-and-security sweep habit that substitutes for a second engineer's review.

Curl First, Build Second: The API Validation Methodology
The one rule that shaped every feature in MistPulse: never write application code against an API response shape you haven't verified yourself, with a real curl call, first.

The Idea: Why I Built MistPulse
How years of troubleshooting networks from a laptop in the field turned into a mobile app project, and the non-negotiable security boundary set before a line of code was written.
Comments
No comments yet. Be the first.