Flow Auth
Passkey-first authentication for Flow applications. One passkey bound to id.flow.industries, usable across all Flow apps, with optional Tempo chain support.
Integration
There are two ways to integrate Flow Auth into your app:
Option 1: Wagmi connector
Best for apps that use wagmi/viem and want standard React hooks (useAccount, useConnect, useSendTransaction).
Install dependencies:
bun add wagmi viem @tanstack/react-query
Set up the config:
import { createConfig, http, createStorage } from "wagmi"
import { tempo } from "viem/chains"
import { flow } from "@flow/auth/client"
export const config = createConfig({
chains: [tempo],
connectors: [
flow({
host: "https://id.flow.industries/dialog",
rpId: "id.flow.industries",
}),
],
storage: createStorage({ storage: localStorage }),
transports: {
[tempo.id]: http(),
},
})
Wrap your app:
import { WagmiProvider } from "wagmi"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { config } from "./config"
const queryClient = new QueryClient()
createRoot(document.getElementById("root")!).render(
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</WagmiProvider>
)
Use in components:
import { useAccount, useConnect, useConnectors, useDisconnect } from "wagmi"
function Auth() {
const { connect, isPending, error } = useConnect()
const [connector] = useConnectors()
const account = useAccount()
const { disconnect } = useDisconnect()
if (account.isConnected) {
return (
<div>
<p>Connected: {account.address}</p>
<button onClick={() => disconnect()}>Sign out</button>
</div>
)
}
return (
<div>
{/* Sign up — opens dialog with username + passkey creation */}
<button onClick={() => connect({
connector,
capabilities: { type: "sign-up" },
} as any)}>
Sign up
</button>
{/* Sign in — opens dialog, passkey prompt immediately */}
<button onClick={() => connect({
connector,
capabilities: { type: "sign-in" },
} as any)}>
Sign in
</button>
{/* Welcome screen — opens dialog, user chooses */}
<button onClick={() => connect({ connector })}>
Sign in with Flow
</button>
{error && <p>{error.message}</p>}
</div>
)
}
After connecting, the user's Tempo address is available via useAccount(). The passkey credential is persisted in localStorage — useAccount() returns the address on page reload without re-prompting.
Option 2: Direct dialog host
Best for apps that don't use wagmi, or want full control over the dialog lifecycle.
import { createDialogHost } from "@flow/auth/client"
const dialog = createDialogHost({
host: "https://id.flow.industries/dialog",
})
Sign up:
const result = await dialog.request("wallet_connect", [
{ capabilities: { createAccount: true } },
])
Sign in:
const result = await dialog.request("wallet_connect", [
{ capabilities: { signIn: true } },
])
Welcome screen (user chooses sign up or sign in):
const result = await dialog.request("wallet_connect", [
{ capabilities: {} },
])
Check session:
const res = await fetch("https://id.flow.industries/api/me", {
credentials: "include",
})
const { session } = await res.json()
Close the dialog:
dialog.close()
dialog.destroy()
How it works
- Your app opens the Flow Auth dialog (iframe at
id.flow.industries)
- The dialog handles passkey creation/authentication + username onboarding
- A session cookie is set on
id.flow.industries
- The credential (id + publicKey) is returned to your app
- For Tempo chain apps:
Account.fromWebAuthnP256(credential, { rpId }) reconstructs a signing account from the stored passkey
The passkey is bound to id.flow.industries via WebAuthn's rpId, so the same passkey works across all Flow apps (flow.game, flow.talk, etc.) through the shared dialog.
Server endpoints
GET /api/config | Returns { rpId, rpName } |
GET /api/me | Returns current session/user (requires session cookie) |
GET /api/account/username/:name | Check username availability |
GET /api/account/account/:address | Look up username by address |
GET /keys/challenge | Generate WebAuthn challenge |
GET /keys/:credentialId | Get stored public key |
POST /keys/:credentialId | Store public key (with attestation verification) |
POST /api/auth/passkey/register | Create user + passkey + session |
POST /api/auth/passkey/challenge | Generate sign-in challenge |
POST /api/auth/passkey/verify | Verify passkey signature + create session |
Development
bun install
bun run db:push
bun run dev
Playground at http://localhost:5176 — has both Wagmi and Direct integration demos.