feat: auth-only mode for identity-only connect flow#63
feat: auth-only mode for identity-only connect flow#63volod-vana wants to merge 1 commit intomainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Adds `mode: "auth"` option to ConnectConfig that lets builders authenticate users (get their wallet address) without requiring DataConnect or the full data grant flow. After Privy auth, the Account Portal claims and approves the session directly on the relay, returning the user's embedded wallet address to the SDK. Zero changes to Session Relay — uses existing claim/approve endpoints with a synthetic `identity.auth` scope. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
41b2cc9 to
74dd599
Compare
tnunamak
left a comment
There was a problem hiding this comment.
Review via Claude Code's next-prompt — an agent that picks the most valuable next action from connected personal data.
Overall this is a clean, well-scoped addition. The SDK-side changes follow existing patterns well and the portal changes are reasonable. A few issues worth addressing before merge:
Security / correctness concerns
1. relayUrl in the connect URL leaks the Session Relay origin to the browser
connect() puts relayUrl into the connectUrl query string, and the Account Portal then uses it to make direct fetch() calls from the browser to the Session Relay. This means:
- The Session Relay URL is exposed in the browser's address bar and network tab (minor — it's likely already discoverable).
- More importantly, the portal makes unauthenticated
POST /v1/session/claimandPOST /v1/session/${sessionId}/approvecalls directly from the browser. The normal connect flow has the desktop app (DataConnect) call these endpoints. Are these relay endpoints designed to accept browser CORS requests? If not, this will fail in production. If they are, is there any rate-limiting or origin validation on them? A malicious page that intercepts thesessionId+secretcould claim and approve the session, impersonating the user.
The secret is the only proof-of-possession here. Since auth-only mode skips master key signing, the security model relies entirely on the secret remaining confidential. This is probably acceptable for the use case but worth documenting.
Suggestion: Consider using the portal's own backend (e.g., an API route) to proxy the claim/approve calls instead of having the browser call the relay directly. This would keep the relay URL server-side and allow signed requests (which the existing createRequestSigner pattern supports). Alternatively, document why direct browser calls are acceptable here.
2. mode on ConnectHandoffContext is typed string | null but should be a union
In handoff-contract.ts, mode is string | null. The SDK types already constrain it to "auth" | "connect". The portal side should at least validate the value before acting on it — right now any string value for mode would trigger isAuthOnly = handoffContext?.mode === "auth", which is fine defensively, but the type should be "auth" | "connect" | null for self-documentation and to match the SDK.
API design
3. scopes becoming optional on ConnectConfig is a breaking behavioral change for connect mode
Making scopes optional is fine for auth mode, but for mode: "connect" (the default), calling connect({ privateKey }) without scopes will now silently send an empty scopes array to the relay instead of getting a TypeScript error. This could create confusing sessions that request no data.
Suggestion: Either validate at runtime that scopes is non-empty when mode !== "auth" (throw a ConnectError with CONFIG_INVALID), or use a discriminated union type:
export type ConnectConfig =
| { mode?: "connect"; privateKey: `0x${string}`; scopes: string[]; /* ...rest */ }
| { mode: "auth"; privateKey: `0x${string}`; scopes?: string[]; /* ...rest */ };This would preserve the existing compile-time safety for connect mode while making scopes optional only for auth mode.
4. The synthetic grantId: "auth-only" may break downstream consumers
The approve call sends grantId: "auth-only". When the SDK consumer polls and receives the grant via useVanaConnect or pollUntilComplete, isValidGrant() will validate it (it checks grantId.length > 0, which passes). But if a consumer then passes this grant to getData(), it will attempt to fetch data with grantId: "auth-only", which will presumably fail at the Personal Server with an opaque error.
Suggestion: Document clearly in the TSDoc for mode: "auth" that the returned grant's grantId is synthetic and should NOT be passed to getData(). Even better, consider adding a grant.mode field or a helper like isAuthOnlyGrant(grant) so consumers can programmatically distinguish.
Testing gaps
5. No SDK-side tests for auth mode
The connect.test.ts file has no tests for:
connect({ privateKey, mode: "auth" })— verifying thatmodeandrelayUrlappear in theconnectUrlconnect({ privateKey, mode: "auth" })— verifying that scopes default to["identity.auth"]connect({ privateKey, mode: "connect" })— verifying backward compatibility with explicit mode
These are straightforward to add given the existing test patterns.
6. No portal-side tests for the auth-only effect
The handoff-contract.test.ts test file adds mode: null and relayUrl: null to the createContext helper (good), but there are no tests for:
parseFromSearchParamswithmode=auth&relayUrl=https://relay.example.comin the query string- Verifying
modeandrelayUrlsurvive serialization/deserialization round-trips - Verifying
createHandoffQueryParamsincludesmodeandrelayUrlwhen present
Minor
- The "Connected" success UI in
connect-page-client.tsxuses a raw HTML entity (✓) for the checkmark. Consider using the existing design system components/icons if available for consistency. - The
isAuthOnlyvariable is derived inuse-connect-page.tsbutisAuthOnlyis not part ofConnectPageViewor documented in the hook's return type — it's just a boolean. Adding a brief TSDoc comment on the return value would help.
Summary
The core design — reusing existing relay endpoints with a synthetic scope to avoid DataConnect — is pragmatic and avoids unnecessary backend changes. The main risks are around the browser making direct relay calls (CORS / security surface), the lack of compile-time safety for scopes in connect mode, and missing test coverage for the new code paths. I'd want items 1, 3, and 5 addressed before merging.
Summary
mode: "auth"option toConnectConfigthat authenticates users and returns their wallet address without requiring DataConnect or the full data grant flow/claimand/approveendpoints with a syntheticidentity.authscopeChanges
SDK (
src/):ConnectConfig.mode?: "auth" | "connect"— new optional fieldConnectConfig.scopes— now optional (defaults to["identity.auth"]for auth mode)connect()passesmodeandrelayUrlto the Account Portal via connectUrl paramsAccount Portal (
connect/):handoff-contract.ts— threadsmode+relayUrlthrough the handoff context (survives auth redirects)use-connect-page.ts— auth-only flow: after Privy auth + wallet ready, calls/claimthen/approvedirectly, skips master key signingpage.tsx— shows "Connected" confirmation instead of DataConnect deep link for auth-only modeUsage
Backward compatibility
connect({ scopes: [...] })flow is completely untouchedTest plan
mode: "auth"→ Privy login → address returned🤖 Generated with Claude Code