-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Summary
Add the ability to request a signature from users during the login/connection flow, similar to dapp-kit's OnConnectV2Callbacks. This enables authentication patterns like Sign-In with Ethereum (SIWE), nonce-based authentication, and timestamp-based session verification.
Motivation
Many dApps need to verify user ownership of an address during login for authentication purposes. Currently, developers must implement a separate signing step after connection, which leads to:
- Poor UX (extra step after login)
- Complexity in handling session management
- Inability to use modern auth patterns like SIWE
- No way to prevent replay attacks with timestamps/nonces
Current Behavior
- User connects wallet (via DappKit, Privy OAuth, or VeChain ecosystem)
- Connection completes
- Developer must separately request signature for authentication
- User must sign in a separate step
Expected Behavior
- User initiates login with callbacks configured
- Connection happens (wallet selection, OAuth flow, etc.)
- Automatically prompt for signature with dynamic data (timestamp, nonce, etc.)
- Callback receives signature along with connection details
- Developer can send signature to backend for authentication
Known Issues
-
Currently dappkit v2Api provides a callback where the devs can pass a message to sign (and will do it in one go: login and sign). This only works in veworld mobile though, so we need to handle all the other scenarios: social login, veworld extension, synch2, wallet connect (?) where both operations cannot work in one single sign, so we will first need to connect and then prompt again to sign (which is what currently happens with veworld extension when passing signCertificateOnConnect prop).
-
Another issue is that this signing messsage/certifcate/typed data cannot be set in the provider, because if it has a dynamic data like a timestamp or random number provided by the backend then the entire provider would need to be reloaded completly, so it needs to be some sort of callback
-
When connecting with social login I'm not sure this can work entirely, because after login we will also need to get the smart account address, and if that address is part of the certifcate that the dev needs to fill with info I'm not sure how this could work.
-
Social login can sign ONLY typed data, while veworld can sign also certificates, so everything needs to be aligned.
-
We need to handle situations where the devs can require to login/sign again because the jwt expired (the dappkit v2Api does it with the "external" prop in the provider)
Possible solutions
-
We invent an api and we force devs to use a specific format, that we know works with all, and the devs need to adapt; this way it could be us that we fill the typed data with content, generate timestamp or whatever (and maybe we allow devs to pass just a single random value or something like that).
-
We could allow this feature ONLY FOR MOBILE VEWORLD users
Acceptance Criteria (for full implementation)
- DappKit connections can request signature during connection
- Privy OAuth connections can request signature after wallet creation
- VeChain ecosystem connections support signature on login
- Callbacks support dynamic data (timestamps, nonces from backend)
- Signature failures don't block connection (graceful degradation)
- Examples provided for all connection methods in example apps
- TypeScript types are fully compatible with
@vechain/dapp-kit - Works with both embedded wallets (silent) and external wallets (interactive)
- Documentation includes security best practices
Related Issues
- Add error handling guidelines for signature rejection
- Consider adding loading states for signing process
References
- DappKit v2 API: https://github.com/vechain/vechain-dapp-kit
- Sign-In with Ethereum (SIWE): https://login.xyz/
- EIP-712 Typed Data: https://eips.ethereum.org/EIPS/eip-712
- Privy Embedded Wallets: https://docs.privy.io/guide/react/wallets/embedded
Basic Example
Use Case 1: Backend Session Authentication
// Frontend
const callbacks = {
onConnectRequest: async () => ({
domain: { name: 'My dApp', version: '1' },
types: { Session: [{ name: 'timestamp', type: 'uint256' }] },
primaryType: 'Session',
message: { timestamp: Date.now() / 1000 },
}),
onConnectResponse: async (source, response) => {
// Backend verifies signature and creates session
await createSession(response.address, response.signature);
},
};
Use Case 2: Nonce-Based Auth (Prevent Replay Attacks)
// Frontend
const callbacks = {
onConnectRequest: async () => {
const nonce = await fetch('/api/auth/nonce').then(r => r.text());
return {
domain: { name: 'My dApp', version: '1' },
types: { Auth: [{ name: 'nonce', type: 'string' }] },
primaryType: 'Auth',
message: { nonce },
};
},
onConnectResponse: async (source, response) => {
await verifyAndLogin(response);
},
};
Use Case 3: Terms of Service Agreement
const callbacks = {
onConnectRequest: async () => ({
domain: { name: 'My dApp', version: '1' },
types: {
Agreement: [
{ name: 'statement', type: 'string' },
{ name: 'version', type: 'string' },
],
},
primaryType: 'Agreement',
message: {
statement: 'I agree to the Terms of Service',
version: '1.0.0',
},
}),
onConnectResponse: async (source, response) => {
// Store ToS acceptance
await recordAgreement(response.address, response.signature);
},
};