Authentication best practices
Always useauthenticate() — never skip verification. If you’re accessing any user-specific data or gating any feature behind authentication, verify the session on your backend first. A client-side authenticated state is not a security control.
Call verifyAuth() on your backend for every protected request. Don’t rely on a client-provided wallet address or session flag. The only way to confirm a user is genuinely authenticated is to verify the access token server-side.
Backend security
Transaction security
Explain intent to users before they approve. The SocketFi approval screen surfaces what you give it. Your application should build UI context around every transaction — show the action in plain language, the assets involved, the destination, and the expected outcome — before the user even reaches the approval screen. Avoid:requestTransaction(). Confirm that addresses are valid, amounts are within expected ranges, and required arguments are present. Surface these errors in your UI before initiating the transaction workflow — don’t let malformed requests reach the approval screen.
Handle rejections gracefully. A user who cancels the approval screen made an intentional, valid choice. Don’t show them an error. Reset the UI silently and let them try again at their own pace. Aggressive re-prompting erodes trust and trains users to approve without reading.
Never auto-approve or chain approvals without visibility. Each transaction that modifies on-chain state must have a separate, explicit approval. Don’t construct flows that chain multiple approvals so quickly that users can’t review each one.
Verify results before updating state. Only update balances, positions, or other application state after confirming result.success === true. Updating state optimistically before confirmation can create a false picture of reality if the transaction fails post-approval.
Wallet security
Treat the wallet address as the canonical user identity. The wallet address is stable across passkey rotations and device changes. Use the address returned byverifyAuth() — never trust a wallet address provided by the client without server-side verification.
Don’t assume authorization will succeed. Policy violations, nonce conflicts, and fee failures can all cause wallet authorization to fail even after user approval. Build your application to handle these failures without creating dead ends.
Monitor for unexpected authorization failures. A spike in TRANSACTION_REJECTED or POLICY_VIOLATION errors from a specific wallet may indicate an attack attempt, a misconfigured policy, or a bug in your transaction construction. Emit these failures to your monitoring system.
Recovery security
Recovery is the highest-risk operation in the SocketFi lifecycle. It replaces the passkey credential bound to a wallet — if misused, it could allow an attacker to take control of a user’s wallet. Treat it accordingly. Treat recovery initiation as a security event. Log every recovery request with timestamp, originating IP, and wallet address. Alert on unusual patterns (many recovery requests in a short window, requests from new geographies, etc.). Notify users out-of-band when recovery is initiated. If you have the user’s email or phone number, send a notification when a recovery request is started — not just when it completes. Unexpected notifications alert users to attempts they didn’t initiate. Test your recovery flow in staging before production. Recovery is rarely used but critical when needed. Broken recovery flows leave users locked out permanently. Maintain verified, tested recovery paths as part of your production readiness checklist. Review failed recovery attempts. Repeated failures may indicate fraud, brute-force attempts, or a misconfigured recovery setup. These deserve human review, not just automated logging.Common security mistakes to avoid
Trusting frontend authentication state
Trusting frontend authentication state
Client-side code can be manipulated. A
isAuthenticated: true flag in a React state store or localStorage proves nothing to your backend. Always call verifyAuth() for every protected server request.Using client-provided wallet addresses
Using client-provided wallet addresses
Never use a wallet address supplied by the client as a trusted identity. An attacker could provide any wallet address. Always derive the wallet address from the verified access token via
verifyAuth().Assuming requestTransaction() always succeeds
Assuming requestTransaction() always succeeds
requestTransaction() resolves — not rejects — for many failure conditions. Checking result.success is mandatory. Updating state before this check can leave your UI in an inconsistent state.Showing raw error codes to users
Showing raw error codes to users
Error codes like
TRANSACTION_REJECTED or INVALID_SIGNATURE are useful for developers but confusing or alarming for users. Map error codes to friendly, actionable messages in your UI.Skipping input validation before transactions
Skipping input validation before transactions
Sending a malformed
contractId or invalid args type to requestTransaction() causes an error before any user interaction. Validate your inputs early, surface errors in the UI, and only call requestTransaction() when inputs are confirmed valid.Not notifying users of credential changes
Not notifying users of credential changes
Credential rotations and recovery completions are security events. Users who don’t receive notifications can’t detect unauthorized access. Always surface these events, ideally through both in-app UI and an out-of-band channel.
Storing tokens in localStorage
Storing tokens in localStorage
localStorage is accessible to any JavaScript running on the page, making it a target for XSS attacks. Store session tokens in memory or use httpOnly cookies with appropriate security flags.