The hosted UI provides a production-ready passkey interface that handles all WebAuthn complexity. This guide covers implementation for web and mobile applications.
Passkeys are currently only live on devnet. Mainnet support is coming soon.
The appName determines how the passkey will be named when created on your user’s device. Use your application’s name or a clear identifier so users can easily recognize it.
Web Integration (React/Next.js) - Hosted UI
Initiate Passkey Session
Request a passkey session from your backend, specifying the action (create
or auth
), and receive a hosted UI URL.
appName determines how the passkey will be named when it is created on your user’s device. Use your application’s name or a clear identifier so users can easily recognize it.
const requestPasskeySession = async (action: "create" | "auth") => {
const appName = "YourAppName";
// Generate a session key
const keypair = Keypair.generate();
const sessionKey = {
key: keypair.publicKey.toBase58(),
expiration: 900, // 15 minutes
};
const redirectUrl = encodeURIComponent(window.location.origin + "/");
const passkeyData = {
action: action,
sessionKey,
env: "devnet", // or 'mainnet'
metaInfo: {
appName: appName,
redirectUrl: redirectUrl,
},
};
// Get the passkey URL from your API
const response = await fetch(
`https://developer-api.squads.so/api/v1/passkeys`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-squads-network": "devnet", // or 'mainnet'
"Authorization": "Bearer YOUR_API_KEY",
},
body: JSON.stringify({
action: action,
sessionKey,
metaInfo: {
appName: appName,
redirectUrl: redirectUrl,
},
}),
}
);
const data = await response.json();
setPasskeyUrl(data.url);
setShowIframe(true);
};
Embed Hosted UI
Add the hosted Passkey UI to your app using an iframe
with the correct WebAuthn permissions.
<iframe
allow="publickey-credentials-get *; publickey-credentials-create *"
src={passkeyUrl}
style={{ display: "none" }}
onError={(e) => {
console.error("Iframe error:", e);
}}
onLoad={() => {
console.log("Iframe loaded successfully");
}}
/>
Handle Authentication Result
Listen for messages from the iframe to receive the session token or handle errors, then use the session token as a signer in Smart Account operations.
On successful authentication, the iframe will post a message of type authz_complete
containing a sessionToken
and the onchain passkey address. On error, a message of type authz_error
will be sent with an error description. You can also extract these values from the redirect URL if using a full-page redirect.
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data.type === "authz_complete") {
// event.data.sessionToken: Use this as a signer for Smart Account operations
// event.data.passkeyAddress: The onchain address of the created or authenticated passkey
console.log("Authorization complete:", event.data);
const sessionToken = event.data.sessionToken;
const passkeyAddress = event.data.passkeyAddress;
// Handle successful authentication
} else if (event.data.type === "authz_error") {
// event.data.error: Error description
console.log("Authorization error:", event.data.error);
// Handle authentication error
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
React Native Integration - Hosted UI
Initiate Passkey Session
Request a passkey session from your backend and receive the hosted UI URL.
import { Linking } from 'react-native';
const appName = "YourAppName";
const action = "create"; // or "auth"
const apiKey = "YOUR_API_KEY";
const network = "devnet"; // or 'mainnet'
const redirectUri = Linking.createURL("login");
// Generate a session key
const keypair = Keypair.generate();
const sessionKey = {
key: keypair.publicKey.toBase58(),
expiration: 900, // 15 minutes
};
const passkeyData = {
action,
sessionKey,
metaInfo: {
appName,
redirectUrl: redirectUri,
},
};
// Get the passkey URL
const response = await fetch("https://developer-api.squads.so/api/v1/passkeys", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`,
"x-squads-network": network,
},
body: JSON.stringify(passkeyData),
});
const data = await response.json();
Open Hosted UI in Browser
Launch the Passkey UI in the device browser using WebBrowser.openAuthSessionAsync
.
const result = await WebBrowser.openAuthSessionAsync(data.url, redirectUri);
Process Redirect Result
Handle the redirect and extract the session token from the result for use as a signer in Smart Account operations.
// result is encoded in url
const parsed = LinkingExpo.parse(result.url);
const status = parsed.queryParams?.status;
if (status === "success") {
// parseUrl for externally signed account
} else {
// handle error
}
WebAuthn Configuration Requirements
Required Settings
The on-chain passkey program has specific requirements that must be met for successful registration and authentication:
User Presence Required:
userPresent
must be true
in the WebAuthn authenticator response
- This applies to both passkey creation and subsequent authentications
- The authenticator must verify user presence through biometric, PIN, or physical interaction
Algorithm Requirement:
- Only ES256 (algorithm
-7
) is supported
- No other cryptographic algorithms are accepted by the on-chain program
WebAuthn Configuration Example:
// For passkey creation
const createOptions = {
publicKey: {
challenge: challenge, // From API URL
rp: { name: "Your App" },
user: { /* user info */ },
pubKeyCredParams: [{ alg: -7, type: "public-key" }], // REQUIRED: Only -7 (ES256) supported
authenticatorSelection: {
userVerification: "preferred", // Ensures user presence
requireResidentKey: true
},
attestation: "direct"
}
};
// For authentication
const getOptions = {
publicKey: {
challenge: challenge, // From API URL
userVerification: "preferred", // Ensures user presence
timeout: 60000
}
};
Handling Registration Failures
If local passkey creation succeeds but submission fails:
- The passkey exists locally but has no associated on-chain account
- Solution: Re-request a new URL with
action: "create"
and submit again
- The existing local passkey will work with the new challenge
- No need to recreate the local passkey
Common failure scenarios:
- Challenge expired (60-second timeout)
- Network issues during submission
- Invalid WebAuthn response format
Best Practices
-
Challenge Handling:
- Parse challenge from URL parameters
- Use challenge directly - it’s already base64 encoded
- Never re-encode the challenge
- Return complete WebAuthn response objects
- Complete WebAuthn ceremony within 60 seconds of URL generation
-
Session Management:
- Generate unique session keys for each authentication
- Implement proper expiration handling
- Securely store session tokens
-
Error Handling:
- Implement comprehensive error handling
- Provide user-friendly error messages
- Log errors for debugging
-
User Experience:
- Use clear, recognizable app names
- Provide loading states during authentication
- Handle both success and failure scenarios
-
Security:
- Validate all inputs
- Use HTTPS for all communications
- Implement proper CORS policies
- Never expose API keys in client-side code
For more details, see the Create or Authenticate Passkey endpoint documentation.
Advanced Integration
Need Your Own Custom Domain? If you need to host passkey flows on your own domain (e.g., auth.yourcompany.com
) for branding or compliance requirements, see Custom Domain.
Responses are generated using AI and may contain mistakes.