Skip to content

feat: add headless mode support to controller SDK#2315

Merged
tarrencev merged 55 commits intomainfrom
head2
Feb 10, 2026
Merged

feat: add headless mode support to controller SDK#2315
tarrencev merged 55 commits intomainfrom
head2

Conversation

@tarrencev
Copy link
Contributor

@tarrencev tarrencev commented Jan 8, 2026

Adds headless auth support to controller.connect({ username, signer, password? }), performing auth in a hidden keychain iframe and only opening UI for session approval when required.

Keychain connect() routes headless options through a single handler that blocks until approval completes, then triggers onSessionCreated so the parent app updates immediately.

@cartridge/connector now returns the authoritative controller address from connect() and improves disconnect handling to keep @starknet-react/core state in sync.

Verified-session auto-creation logic is centralized and shared across UI connect, standalone session creation, and headless flows; policy processing lives in a non-UI utility.

Adds unit/regression tests for connect routing, verified session creation, and connector state sync; CI runs controller Jest tests.

@vercel
Copy link

vercel bot commented Jan 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
controller-example-next Ready Ready Preview Feb 10, 2026 6:28pm
keychain Ready Ready Preview Feb 10, 2026 6:28pm
keychain-storybook Ready Ready Preview Feb 10, 2026 6:28pm

Request Review

password,
},
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated headless login attempts fail due to singleton iframe

High Severity

The HeadlessLogin component creates a new Controller on each login attempt, but the underlying IFrame class uses singleton-style DOM element management - it checks for an existing "controller" element and won't add a second one. After the first login attempt, subsequent Controller instances have their iframes created but never added to the DOM, so the penpal connection never establishes, this.keychain remains undefined, and connect() immediately returns undefined with a "Not ready to connect" error. Users clicking the login button multiple times will see the first attempt work (or fail for unimplemented reasons) but all subsequent attempts fail instantly.

Fix in Cursor Fix in Web

return authenticateHeadless(
headless.username,
headless.credentials,
).then((result) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Headless mode ignores configured chain, always uses mainnet

High Severity

The authenticateHeadless function accepts an optional chainId parameter that defaults to mainnet, but the connect handler never passes a chainId when calling it. The HeadlessOptions type doesn't include chainId, and neither does ConnectOptions, so even though users configure defaultChainId: SN_SEPOLIA in the Controller SDK, headless authentication always uses mainnet. This causes headless authentication to operate on the wrong chain regardless of configuration.

Additional Locations (1)

Fix in Cursor Fix in Web

# Conflicts:
#	examples/next/src/components/providers/StarknetProvider.tsx
Keychain connect now supports headless options and waits for session approval; make onSessionCreated awaitable and remove controller-side headless approval listeners/pending state.

Also allow claude-code-review to run gh pr commands.
The claude-code-review action exchanges an OIDC token for an app token and requires the workflow file to match default-branch content; changing it in a PR causes 401s.
@tarrencev
Copy link
Contributor Author

Follow-up on the earlier security review notes:

  • Headless EIP-191 (MetaMask/Rabby/Phantom EVM) does not accept an arbitrary address. We always call externalConnectWallet(...) and only proceed if the connected wallet address matches a registered EIP-191 signer (hasMatchingEip191Signer).

  • Password signer: headless credentials are no longer serialized into the /connect URL query params. createConnectUrl() only encodes signupOptions; headless options stay in the in-memory callback store.

@claude
Copy link
Contributor

claude bot commented Feb 10, 2026

Claude finished @tarrencev's task —— View job


Documentation Analysis for Headless Mode Feature ✅

I've successfully analyzed the controller repository changes for the headless mode feature and updated the documentation accordingly.

Tasks Completed:

  • Check existing documentation structure in docs-repo directory
  • Review key controller changes to understand headless mode functionality
  • Identify user-facing API changes that need documentation
  • Update or create documentation files as needed
  • Verify documentation accuracy and completeness

Documentation Updates Completed

1. Updated Getting Started Guide (src/pages/controller/getting-started.mdx)

  • Added comprehensive "Headless Authentication" section with examples for:
    • WebAuthn/Passkey authentication
    • Password authentication
    • OAuth providers (Google, Discord)
    • EVM wallets (MetaMask, Phantom)
  • Included important notes about UI behavior during session approval

2. Enhanced React Integration Guide (src/pages/controller/examples/react.md)

  • Added complete "Headless Authentication" section with:
    • React hook patterns for headless auth
    • Working example component with proper error handling
    • State synchronization with starknet-react
    • Warnings about signer requirements

3. Created Dedicated Headless Authentication Guide (src/pages/controller/headless-authentication.md)

  • Comprehensive documentation covering:
    • All supported authentication methods (WebAuthn, password, OAuth, EVM wallets, WalletConnect)
    • Session approval flow explanation
    • Error handling with HeadlessAuthenticationError
    • Security considerations and best practices
    • Integration patterns for React and server-side use
    • Troubleshooting section
    • Clear distinction from native headless mode

4. Clarified Native Headless Documentation (src/pages/controller/native/headless.md)

  • Updated title to "Native Headless Controller"
  • Added cross-reference to distinguish from web-based headless authentication
  • Clarified this covers C++ bindings for server-side applications

Key Features Documented

The new headless mode enables:

  • Programmatic authentication without opening UI
  • Support for all implemented auth methods (WebAuthn, password, OAuth, EVM wallets)
  • Automatic session approval when policies are unverified
  • Error handling with specific HeadlessAuthenticationError class
  • Seamless integration with existing React/starknet-react applications

Files Ready for Commit

The documentation changes are staged and ready to be committed to the docs repository. The updates accurately reflect the new headless authentication capabilities added in PR #2315 while maintaining consistency with existing documentation style and structure.

--- | Working on branch: claude/pr-2315-20260210-1849

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant