All case studies
iOS app, web and backend

Klaaro Card

A native iOS wallet for business cards that anyone can open without the app. Most of the hard work was in the backend: ownership without accounts, and row-level security that holds up against raw API queries.

Role
PM and lead developer (136 of 149 commits)
Timeline
May 2026 – Sep 2026
Team
4-person team, Apple Developer Academy
30+
beta users before the App Store launch
26
versioned database migrations
SwiftSwiftUISupabasePostgreSQLEdge FunctionsSign in with AppleNext.jsCoreImage
Klaaro Card
01

The product

Klaaro Card is a wallet for professional contact cards. You design your own card, then share it by QR code or by tap. The QR code encodes a klaaro.cards link, so the person scanning it does not need the app: Universal Links open the app when it is installed, and a Next.js page shows the card when it is not.

I was PM and lead developer on a 4-person team at the Apple Developer Academy, working across the SwiftUI app, the web pages and the Supabase backend.

02

Ownership without an account

Nobody wants to create an account just to hand out a business card, so there is no login by default. - Each card is owned by a secret device token stored in the Keychain, synced through iCloud Keychain when available. - Write policies in Postgres row-level security compare that token with a request header. - Sign in with Apple is optional and only adds sync. - On reinstall, the app restores the card through a database function that matches the token and never returns it.

03

The bug that restored a stranger's card

Restore first used a plain query. Cards had two read policies: anyone can read public cards, and owners can read their own card by token. Postgres combines permissive policies with OR, so on a device with no matching token the query still matched every public card, and the app could restore a random stranger's card.

I moved restore into a SECURITY DEFINER function that reads the token header directly and returns only the card that matches, or nothing.

04

Closing a token leak

Row-level security decides which rows a role can read, not which columns. With the public API key, anyone could query the device token column of public cards, and that token is exactly what every write policy trusts, so it would have allowed taking over someone's card. Owner IDs and payment details that owners had chosen to hide were exposed the same way.

I revoked table-wide read access and granted back only the columns public clients need.

That fix broke card sync. The app's upserts made PostgREST read the token column back, which the new grants forbid, so every save failed with a 401. Granting the column again would have reopened the leak. Instead, a database trigger now fills the token from the request header, and the app stopped sending it in the request body. For public clients, the token is now write-only.

05

Work the app should not do

Supabase Edge Functions handle the jobs that should not run with a public key: one sends push notifications when someone receives a card, and another deletes an account and its data.

06

Results

  • 30+ beta users before the App Store launch.
  • Live on the App Store since September 2026.