In order to verify that a user has ownership of a wallet address, Picket requires users to sign a nonce with the wallet's private key. This signed nonce is referred to as a signature. A signature can be decrypted to prove that the user signed it with the private key associated with their wallet address.
Obtaining a signature for a given user is the basis for authentication and authorization in Picket.
Why use Picket for this?
Poorly handled signatures are not secure. Since signatures are obtained client side, the risk of a signature being stolen needs to be considered non-zero. Asking a user to sign the same text repeatedly for example, presents a security risk for your users because anyone who gets access to the signature can then masquerade as the owner of the wallet.
Picket handles generating secure and randomized nonces for you. Each nonce is scoped to a given project and has a limited time to live. This allows for a secure wallet verification experience for your users without any work or maintenance from you.
In addition to security, Picket provides a human-readable message with every nonce. These messages help users understand what they are doing when prompted by their wallet provider to sign the nonce. Messages can be scoped to a project to give the best possible experience to users within your app.
Fetch user nonce
If youโd like to sign the user nonce manually, you can fetch the nonce for signing via this endpoint. Given the resulting nonce, you can proceed to sign the nonce with the userโs private key via any existing convenience library.
If you are using Picket's client-side SDKs, then obtaining a user's signature is handled for you as part of the authorization flow.
However, if you want to handle obtaining user signatures yourself Picket makes that possible too.
Using a Picket SDK (Connect Wallet)
This is the recommended and easiest way to obtain a signature. The connect method fetches a nonce as well as initiating a signature request via a connected wallet on the client side. Returns the connected wallet address and a signature. This operation is done via a picket client library.โโ
Steps for Manually Obtaining Signatures
Using the connect method via a picket SDK for obtaining a signature is the recommended and easier route. However, if you would like to manually sign a nonce for an custom wallet modal integration or for a language that currently lacks picket SDK support, then follow the instructions below to do so.
Connect your user to a wallet provider
Use the nonce endpoint to fetch the random nonce for a given wallet address
Generate a message for the user to sign with Picket.createSigningMessage
Sign the message with the signMessage method on the wallet provider
Use the Picket auth method to verify the signature or implement and use your own auth endpoint
import { ethers } from"ethers";import Picket from"@picketapi/picket-node";constpicket=newPicket("YOUR_SECRET_KEY_HERE");// fetch the EVM provider and chain info via your preferred libray// RainbowKit, web3Modal, wagmi, etcconstprovider="..."constchain="ethereum";constchainId=1;constwallet=awaitnewethers.providers.Web3Provider(provider);constsigner=awaitwallet.getSigner();// Get user's connected wallet addressconstwalletAddress=awaitsigner.getAddress();// fetch the nonceconst { statement,nonce,format } =awaitpicket.nonce({ walletAddress, chain, locale:navigator?.language,});// SIWE-specific metadataconstdomain=window.location.host;consturi=window.location.origin;constissuedAt=newDate().toISOString();constcontext= { domain, uri, issuedAt, chainId, chainType:"ethereum", locale:navigator?.language,};// generate a SIWE-compatible messageconstmessage=Picket.createSigningMessage({ nonce, walletAddress, statement, format,...context,});// Prompt user to click to sign the nonce with their private keyconstsignature=awaitsigner.signMessage(nonce);// done!const { user,accessToken } =picket.auth({ walletAddress, signature, chain, context,});
import bs58 from"bs58";import Picket from"@picketapi/picket-js";constpicket=newPicket("YOUR_PUBLISHABLE_KEY_HERE");// fetch the wallet provider and chain info via your preferred librayconstprovider="..."constchain="solana";constchainId=101;const { publicKey } = provider;constwalletAddress=publicKey.toString();// fetch the nonceconst { statement,nonce,format } =awaitpicket.nonce({ walletAddress, chain, locale:navigator?.language,});// SIWE-specific metadata// SIWE is completely optional on Solana// It is set as the default for Picket projects// so we've included the instructions here// Turn off SIWE from your Picket dashboardconstdomain=window.location.host;consturi=window.location.origin;constissuedAt=newDate().toISOString();constcontext= { domain, uri, issuedAt, chainId, chainType:"ethereum", locale:navigator?.language,};// generate a signing messageconstmessage=Picket.createSigningMessage({ nonce, walletAddress, statement, format,...context,});constmessageBytes=newTextEncoder().encode(message);constsignatureBytes=awaitprovider.signMessage(messageBytes);constsignature=bs58.encode(signatureBytes);
Custom Server-Side Auth
If you want to verify user's signatures and authenticate them yourself, you can implement it using the Picket server-side SDKs, like @picketapi/picket-node
Use the nonce endpoint to fetch the current nonce for a given wallet address
Re-generate the expected message withPicket.createSigningMessage
Use a web3 utilities library to verify the signature is signed by the intended wallet address
import { ethers } from"ethers";import Picket from"@picketapi/picket-node";constpicket=newPicket("YOUR_SECRET_KEY_HERE");// Insert wallet address to verify as signer of message hereconstwalletAddress="0x...abc";constsignature="SUPER_SECRET_SIGNATURE";// If using SIWE, make sure to send the client-side SIWE context with// the backendconstcontext= {}const { nonce,statement,format } =awaitpicket.nonce({ walletAddress })constmessage=Picket.createSigningMessage({ nonce, walletAddress, statement, format,...context,});constsigner=ethers.utils.verifyMessage(message, signature);// check if the walletAddress is the signerif (signer !== walletAddress) {console.error(`invalid signature: want signer ${walletAddress}; got signer ${signer}`);}
import { ethers } from"ethers";import Picket from"@picketapi/picket-node";constpicket=newPicket("YOUR_SECRET_KEY_HERE");constchain="solana";// Insert wallet address to verify as signer of message hereconstwalletAddress="0x...abc";constsignature="SUPER_SECRET_SIGNATURE";// If using SIWE, make sure to send the client-side SIWE context with// the backendconstcontext= {}const { nonce,statement,format } =awaitpicket.nonce({ chain, walletAddress })constmessage=Picket.createSigningMessage({ nonce, walletAddress, statement, format,...context,});constmessageBytes=newTextEncoder().encode(message);constwalletAddressBytes=bs58.decode(walletAddress);constsignatureBytes=bs58.decode(signature);constsuccess=nacl.sign.detached.verify( messageBytes, signatureBytes, walletAddressBytes);// check if the walletAddress is the signerif (!success) {console.error("invalid signature");}