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";
const picket = new Picket("YOUR_SECRET_KEY_HERE");
// fetch the EVM provider and chain info via your preferred libray
// RainbowKit, web3Modal, wagmi, etc
const provider = "..."
const chain = "ethereum";
const chainId = 1;
const wallet = await new ethers.providers.Web3Provider(provider);
const signer = await wallet.getSigner();
// Get user's connected wallet address
const walletAddress = await signer.getAddress();
// fetch the nonce
const { statement, nonce, format } = await picket.nonce({
walletAddress,
chain,
locale: navigator?.language,
});
// SIWE-specific metadata
const domain = window.location.host;
const uri = window.location.origin;
const issuedAt = new Date().toISOString();
const context = {
domain,
uri,
issuedAt,
chainId,
chainType: "ethereum",
locale: navigator?.language,
};
// generate a SIWE-compatible message
const message = Picket.createSigningMessage({
nonce,
walletAddress,
statement,
format,
...context,
});
// Prompt user to click to sign the nonce with their private key
const signature = await signer.signMessage(nonce);
// done!
const { user, accessToken } = picket.auth({
walletAddress,
signature,
chain,
context,
});
import bs58 from "bs58";
import Picket from "@picketapi/picket-js";
const picket = new Picket("YOUR_PUBLISHABLE_KEY_HERE");
// fetch the wallet provider and chain info via your preferred libray
const provider = "..."
const chain = "solana";
const chainId = 101;
const { publicKey } = provider;
const walletAddress = publicKey.toString();
// fetch the nonce
const { statement, nonce, format } = await picket.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 dashboard
const domain = window.location.host;
const uri = window.location.origin;
const issuedAt = new Date().toISOString();
const context = {
domain,
uri,
issuedAt,
chainId,
chainType: "ethereum",
locale: navigator?.language,
};
// generate a signing message
const message = Picket.createSigningMessage({
nonce,
walletAddress,
statement,
format,
...context,
});
const messageBytes = new TextEncoder().encode(message);
const signatureBytes = await provider.signMessage(messageBytes);
const signature = 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";
const picket = new Picket("YOUR_SECRET_KEY_HERE");
// Insert wallet address to verify as signer of message here
const walletAddress = "0x...abc";
const signature = "SUPER_SECRET_SIGNATURE";
// If using SIWE, make sure to send the client-side SIWE context with
// the backend
const context = {}
const { nonce, statement, format } = await picket.nonce({ walletAddress })
const message = Picket.createSigningMessage({
nonce,
walletAddress,
statement,
format,
...context,
});
const signer = ethers.utils.verifyMessage(message, signature);
// check if the walletAddress is the signer
if (signer !== walletAddress) {
console.error(`invalid signature: want signer ${walletAddress}; got signer ${signer}`);
}
import { ethers } from "ethers";
import Picket from "@picketapi/picket-node";
const picket = new Picket("YOUR_SECRET_KEY_HERE");
const chain = "solana";
// Insert wallet address to verify as signer of message here
const walletAddress = "0x...abc";
const signature = "SUPER_SECRET_SIGNATURE";
// If using SIWE, make sure to send the client-side SIWE context with
// the backend
const context = {}
const { nonce, statement, format } = await picket.nonce({ chain, walletAddress })
const message = Picket.createSigningMessage({
nonce,
walletAddress,
statement,
format,
...context,
});
const messageBytes = new TextEncoder().encode(message);
const walletAddressBytes = bs58.decode(walletAddress);
const signatureBytes = bs58.decode(signature);
const success = nacl.sign.detached.verify(
messageBytes,
signatureBytes,
walletAddressBytes
);
// check if the walletAddress is the signer
if (!success) {
console.error("invalid signature");
}