Sign-in UX
One approval in the Vault, then back in your app. Everything on this page serves that.
Signing in with Flowsta is a different shape from a password form: your app asks, the person approves in their own Vault, and nothing is typed. The shape is simple, but the surfaces are many (your app, the Vault, a browser, a phone), and most sign-in trouble we have seen came from an app saying the wrong thing about a state it could not see. This page is what we learned building our own apps.
The five states, and what to say
Your app learns the Vault's state from getVaultStatus() (desktop, over loopback) or from the errors a sign-in call throws. Say the one true thing for each state, and nothing more.
| State | How you know | Say | Do not say |
|---|---|---|---|
| Not found (not installed, or installed but closed) | running: false, VaultNotFoundError | "Open Flowsta Vault, or get it. This page notices when it is ready." | "Install Flowsta Vault." A closed Vault and a missing one look the same over loopback; a person who has it must never be told to get it. |
| Locked | unlocked: false, VaultLockedError | "Your Vault is locked. Unlock it, then sign in." The Vault holds a sign-in request for 55 seconds and shows "A sign-in is waiting" on its unlock screen, so a click before unlocking is not lost. | Anything that sounds like a failure. |
| Ready | unlocked: true | "Your Vault asks you to approve." Then, while waiting: "Waiting for your Vault…" | "Enter your password." There is none. |
| Blocked (a browser refused the loopback request) | blocked: true, VaultBlockedError; in practice Safari, Brave, and Chrome until its local network prompt is answered | Hand the sign-in over to the Vault instead (the relay). If you keep the direct path, say exactly what the prompt is: "If your browser asks whether this page may reach devices on your network, choose Allow." | "Install Flowsta Vault." The Vault may well be running; the browser is in the way. |
| Someone else | IdentityMismatchError | "Your Vault holds a different identity from the one this app is connected to." Offer to switch back, to open as that identity, or to disconnect. | Never link again silently. See Identity Switching. |
The person declining in the Vault is not an error state: "You declined the sign-in in your Vault." Raw codes (link_denied, identity_switched, reserved_prefix) belong in your log, never on screen.
Never probe the Vault from a web page you do not have to
A web page that calls 127.0.0.1:27777 to "check for the Vault" pays for it three times: Chrome raises a local-network permission prompt for your site, Safari and Brave refuse and your page concludes the Vault is missing, and the person meets a second prompt later on login.flowsta.com. Let login.flowsta.com do the probing. It routes per browser (direct where loopback works, the relay where it does not, a code on phones) and it already carries the right copy for every state. Your signed-out page needs one line and the button:
Sign in to see plans Your Flowsta Vault approves the sign-in. No password. [Sign in with Flowsta]
Put the "why a Vault, and how to get one" explanation behind a disclosure for the people who need it, not in front of the people who do not.
One thing at a time
A sign-in that opens a Vault dialog, a browser permission prompt and a protocol-handler prompt at once has lost the person before they have read any of them. Sequence it:
- Start one thing. Show what it is: "Opening Flowsta Vault…".
- Only when that resolved, start the next, and change the line to match: "Approve it in your Flowsta Vault."
- Keep a way out visible at every step: "Can't see it? The request is waiting in your Flowsta Vault window." and, when the Vault cannot be reached at all, "Vault on another computer? Approve from there."
In your desktop app the same rule reads: one Vault dialog per sign-in. For a return sign-in with no dialog at all, send the install key your app linked with (localAgentPubKey): the Vault signs silently for a remembered desktop app only when that key matches, since the Origin header alone proves nothing from a native process. A first-time connection (linkFlowstaIdentity()) shows one dialog; a return sign-in shows one or none, depending on the trust the person granted. If your flow shows two, something is asking twice.
The relay, as the person sees it
The relay exists for browsers that cannot reach the Vault: phones, Safari, Brave. The person sees:
- On a phone: "Flowsta Vault runs on your computer for now. Sign in from this phone with a code." A code like
7GK2-M4QX, typed into the Vault on their computer. Then "Waiting for your Vault… the code lasts 5 minutes." and the page finishes by itself. - On Safari or Brave on the desktop: "Opening Flowsta Vault…" while the
flowsta://link hands the code over, then "Approve it in your Flowsta Vault." Always render the code too, in case the protocol handler is not registered (snap browsers on Linux, a Vault installed but never opened). - In the Vault: one approval dialog naming your app, or a code field if they typed.
Say what is happening in the person's words ("your Vault", "this page updates by itself"), never ours ("relay", "deep link", "loopback").
Device identity and web identity
A desktop app signed in through the Vault holds a device identity: the Vault's agent key, proven by a signature over your challenge. A web app signed in through login.flowsta.com holds a web session: an OAuth code exchanged for a token, with a user id. They are the same person, but your backend only knows that if something joins them. Two honest designs:
- Prove it on your backend. The desktop app's key, the web session's user id, and a record on your side that ties them (the person links the device from the web app once). This is the design behind "link this device to your plan".
- Open the web app signed in from the desktop app (the hand-off, below). No link step for the person, because the web session is minted for the identity the desktop app already proved.
Keep the two words apart in your copy too: a person "signs in" to your app and "connects" the app to their Vault; a "device" is one install; "your plan" lives with the web account.
The hand-off: open your web app signed in
When your desktop app sends the person to your web app (billing, a dashboard, an account page), do not make them sign in again in the browser. From @flowsta/holochain 3.9.0, openWebSignedIn() runs the web sign-in from the desktop app: the Vault signs the web client's challenge over loopback (silently when the person's trust setting allows, otherwise one dialog), the API issues the code, and the browser opens your callback URL with the code in the query and the PKCE verifier in the fragment, which never reaches a server. Your callback page (@flowsta/login-button 0.2.0 or later) exchanges the code as usual.
What the person sees: one click, "Your browser opens signed in.", and the web app, signed in. What the web app should then say, on that first page: who is signed in and from where ("Signed in from Your App on this computer as Alex"), because any link that signs a browser in also lets a crafted link sign it in as someone else, and the name on the page is the check a person can make.
To use it, register the web app as the desktop app's companion in the developer portal (Register your app), and read Open your web app signed in for the code.
Copy rules we hold ourselves to
- Say what happens next, before the click: "Your Vault asks you to approve."
- "Flowsta Vault" on first mention, then "your Vault". "Flowsta identity", never "Flowsta account".
- One door, one label. If "See plans" opens the plans page from Settings, it is "See plans" from the chat too.
- No pitch inside a blocked state. A person who cannot get past a step is not reading your reasons.
- Errors name the fix: "Unlock it and try again." Codes go in the log.
- No jargon a person did not bring: DID, Holochain, PKCE, CSRF, token, loopback, relay.
Next steps
- Desktop App Authentication - the sign-in code for desktop apps, including the hand-off
- @flowsta/holochain -
getVaultStatus,authenticateWithVault,startRelayLogin,openWebSignedIn - Identity Switching - what to do when the Vault holds someone else
- Several Devices - one identity on every device the person owns