HandCash Authentication Integration Guide
Prerequisites
- Create an application in the Developer Dashboard
- Configure callback URLs in your application settings
- Get your credentials (
AppId and AppSecret)
Configure callback URLs in dashboard.handcash.io:
- Select your application
- Go to Settings or Configuration
- Set:
- Authorization Success URL: Where users redirect after authorization (e.g.,
https://yourapp.com/auth/success)
- Authorization Failed URL: Where users redirect if they decline (e.g.,
https://yourapp.com/auth/failed)
Important: Use HTTPS in production. For local development, http://localhost is allowed.
Key Generation
Required Dependency
"@noble/secp256k1": "2.2.3"
Generate Key Pair
import * as secp256k1 from "@noble/secp256k1"
export function generateAuthenticationKeyPair() {
const privateKey = secp256k1.utils.randomSecretKey()
const publicKey = secp256k1.getPublicKey(privateKey, true)
return {
privateKey: Buffer.from(privateKey).toString("hex"),
publicKey: Buffer.from(publicKey).toString("hex"),
}
}
Authentication Flow
Step 1: Generate Keys
const { privateKey, publicKey } = generateAuthenticationKeyPair()
Step 2: Store Private Key
Store the private key securely:
Client: localStorage, sessionStorage, or in-memory
Server: Database, Redis, session, or JWT
// Client example
sessionStorage.setItem('handcash_private_key', privateKey)
// Server example
await db.users.update(userId, { handcashPrivateKey: privateKey })
Step 3: Redirect to HandCash
const authUrl = `https://handcash.io/connect?appId=YOUR_APP_ID&publicKey=${publicKey}`
window.location.href = authUrl
The user will log in and authorize your app on HandCash.
Recommended: Use State Parameter for CSRF ProtectionFor enhanced security, include a state parameter to prevent CSRF attacks. See the State Parameter Security section below.
Step 4: Handle Callback
After authorization, HandCash redirects to your Authorization Success URL. Retrieve the stored private key and validate:
import { getInstance, Connect } from '@handcash/sdk'
const privateKey = sessionStorage.getItem('handcash_private_key') // Retrieve stored key
const sdk = getInstance({ appId: 'YOUR_APP_ID', appSecret: 'YOUR_APP_SECRET' })
const client = sdk.getAccountClient(privateKey)
// Validate by fetching user profile
const { data: profile } = await Connect.getCurrentUserProfile({ client })
console.log('User authenticated:', profile.publicProfile.handle)
Step 5: Store Private Key for Session
Associate the private key with the user’s session:
req.session.handcashPrivateKey = privateKey
// or
await db.users.update(userId, { handcashPrivateKey: privateKey })
How It Works
- Generate a private/public key pair
- Send the public key to HandCash during authorization
- User authorizes your app on HandCash
- Use the private key for all API calls - it serves as your authentication credential
- HandCash validates the private key matches the authorized public key
Check User Permissions
const { data } = await Connect.getPermissions({ client })
console.log('User granted permissions:', data?.items)
Error Handling
try {
const client = sdk.getAccountClient(privateKey)
const profile = await Connect.getCurrentUserProfile({ client })
} catch (error) {
if (error.message.includes('Invalid token')) {
// Invalid private key, redirect to re-authenticate
const { publicKey } = generateAuthenticationKeyPair()
window.location.href = `https://handcash.io/connect?appId=${appId}&publicKey=${publicKey}`
}
}
State Parameter Security
Recommended: Use the state parameter to prevent CSRF attacks and ensure the callback originated from your app session.
What This Means in Practice
1️⃣ App Generates State When Starting Connect
Generate a random, unguessable string and store it in your app session:
import crypto from 'crypto'
// Generate random state
const state = crypto.randomBytes(32).toString('hex')
// Store in session (server-side example)
req.session.handcashState = state
// Or client-side (sessionStorage)
sessionStorage.setItem('handcash_state', state)
Include the state in the redirect URL:
const authUrl = `https://handcash.io/connect?appId=YOUR_APP_ID&publicKey=${publicKey}&state=${state}`
window.location.href = authUrl
2️⃣ Wallet Requires State
The HandCash wallet will:
- Require
state to be present in the authorization request
- Echo the same
state back in the callback URL
- Treat
state as proof that the key came from the same app session that initiated connect
3️⃣ Wallet Redirects Back with Same State
After authorization, HandCash redirects to your callback URL with the state included:
https://yourapp.com/auth/success?userId=...&publicKey=...&state=STATE
4️⃣ App Validates State
Validate that the state matches the one stored in your session:
// Server-side example
app.get('/auth/success', async (req, res) => {
const callbackState = req.query.state
const sessionState = req.session.handcashState
// Validate state matches
if (callbackState !== sessionState) {
return res.status(403).send('Invalid state parameter - possible CSRF attack')
}
// Clear state from session
delete req.session.handcashState
// Continue with authentication...
const privateKey = req.session.handcashPrivateKey
// ... rest of authentication flow
})
// Client-side example
const urlParams = new URLSearchParams(window.location.search)
const callbackState = urlParams.get('state')
const sessionState = sessionStorage.getItem('handcash_state')
if (callbackState !== sessionState) {
console.error('Invalid state parameter - possible CSRF attack')
return
}
// Clear state
sessionStorage.removeItem('handcash_state')
// Continue with authentication...
If state mismatch → reject the request - This indicates a potential CSRF attack.
Security Best Practices
- Use state parameter - Always include and validate
state to prevent CSRF attacks
- Store private keys securely - Use encrypted storage or secure sessions
- Use HTTPS - Always use HTTPS for redirect URLs
- Validate private keys - Check validity before making API calls
- Protect the private key - Treat it as sensitive authentication data