← Back to field notes
Infrastructure6 min readSep 23, 2026

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

Share:XLinkedIn
Laying the Foundation: Architecture and the Security-First Proxy

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.

Architecture diagram: MistPulse app to Supabase edge function proxy to the Mist REST API, token never leaving the proxy layer

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

J

Written by

Jesus Hurtado

Network Architect and Cybersecurity Professional.

Comments

No comments yet. Be the first.

Leave a comment