Overview
Agentik Vault uses Solana wallet signature authentication instead of traditional username/password. This provides cryptographic proof of wallet ownership without exposing private keys.
Authentication flow: Nonce → Wallet Signature → JWT Token
How It Works
Step-by-Step Guide
1. Connect Wallet (Frontend)
The frontend uses @solana/react-hooks with auto-discovery:
import { useWalletConnection } from "@solana/react-hooks" ;
function ConnectButton () {
const { connectors , connect , wallet , status } = useWalletConnection ();
const handleConnect = async ( connector ) => {
await connect ( connector . id );
};
if ( status === "connected" ) {
return < div > Connected: { wallet . account . address } </ div > ;
}
return (
< div >
{ connectors . map (( connector ) => (
< button key = { connector . id } onClick = { () => handleConnect ( connector ) } >
Connect { connector . name }
</ button >
)) }
</ div >
);
}
2. Request Nonce
Once wallet is connected, request a challenge nonce:
const walletAddress = wallet . account . address . toString ();
const domain = window . location . host ;
const response = await fetch ( "/api/auth/nonce" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({ wallet_address: walletAddress , domain }),
});
const { nonce , message } = await response . json ();
// message format: "Sign this message to authenticate with Agentik Vault\nNonce: abc123\nDomain: agentikvault.com"
3. Sign Message
Request wallet to sign the message:
const messageBytes = new TextEncoder (). encode ( message );
const signature = await wallet . signMessage ( messageBytes );
const signatureBase58 = bs58 . encode ( signature );
Make sure to encode the message as UTF-8 bytes before signing. The signature must be base58-encoded for transmission.
4. Verify and Get JWT
Send the signature to backend for verification:
const loginResponse = await fetch ( "/api/auth/login" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({
wallet_address: walletAddress ,
signature_base58: signatureBase58 ,
nonce: nonce ,
}),
});
const { token , user_id , wallet_address } = await loginResponse . json ();
// Store JWT in localStorage
localStorage . setItem ( "agentik.auth" , JSON . stringify ({
token ,
walletAddress: wallet_address ,
userId: user_id ,
}));
5. Use JWT for API Calls
Include JWT in Authorization header for authenticated requests:
const token = JSON . parse ( localStorage . getItem ( "agentik.auth" )). token ;
const response = await fetch ( "/api/subscription/status" , {
headers: {
"Authorization" : `Bearer ${ token } ` ,
"Content-Type" : "application/json" ,
},
});
Backend Verification
The backend verifies signatures using tweetnacl:
import nacl from "tweetnacl" ;
import bs58 from "bs58" ;
function verifySignature ( message : string , signature : string , publicKey : string ) : boolean {
const messageBytes = new TextEncoder (). encode ( message );
const signatureBytes = bs58 . decode ( signature );
const publicKeyBytes = bs58 . decode ( publicKey );
return nacl . sign . detached . verify ( messageBytes , signatureBytes , publicKeyBytes );
}
Signature verification is deterministic and cryptographically secure. No password storage required.
JWT Token Structure
The JWT token contains:
{
"userId" : "uuid-v4" ,
"walletAddress" : "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU" ,
"iat" : 1705123456 ,
"exp" : 1705209856
}
userId : Database user ID (UUID)
walletAddress : Solana wallet public key
iat : Issued at (Unix timestamp)
exp : Expires at (Unix timestamp, 24 hours from iat)
Security Best Practices
Never expose private keys Signatures are created by the wallet extension. Your private keys never leave the browser extension’s secure storage.
Nonce replay protection Each nonce is single-use and expires after 5 minutes. Backend tracks used nonces in database.
JWT expiration Tokens expire after 24 hours. Users must re-authenticate by signing a new message.
Domain binding Message includes domain to prevent signature reuse across different sites.
Troubleshooting
Wallet won't sign message
Check wallet is unlocked
Ensure you’re on the correct Solana network (mainnet-beta)
Try disconnecting and reconnecting wallet
Check browser console for errors
Token may be expired (check exp claim)
Token may be invalid (corrupted localStorage)
Clear localStorage and re-authenticate
Signature verification failed
Message format must match exactly (check encoding)
Signature must be base58-encoded
Wallet address must match signer’s public key
Next Steps
Subscription Management Learn how to subscribe to a tier and manage your subscription