-
Notifications
You must be signed in to change notification settings - Fork 0
Frontend implementation guidelines
Julien Béranger edited this page Jul 31, 2024
·
1 revision
In a real application, the nonce plays a crucial role in preventing replay attacks and ensuring the freshness of each authentication request. Here's how it typically works with a frontend:
Initial Nonce Retrieval:
- When a user wants to authenticate, the frontend should first query the current nonce for the user's address from the smart contract.
- This can be done by calling the getNonce(userAddress) function.
Signature Creation:
- The frontend uses this retrieved nonce to create the message that the user needs to sign.
- The message includes the user's address and the current nonce.
User Signing:
- The user signs this message using their wallet (e.g., MetaMask).
Sending Authentication Request:
- The frontend sends the signed message along with the user's address to your backend or directly to the smart contract (via a relayer).
Nonce Increment:
- If the authentication is successful, the smart contract automatically increments the nonce for that user.
Subsequent Authentication Attempts:
- For any future authentication attempts, the frontend must always fetch the latest nonce before creating a new signature.
Here's a simplified example of how this might look in a React frontend:
import { ethers } from 'ethers';
interface AuthenticatorContract extends ethers.Contract {
getNonce(address: string): Promise<ethers.BigNumber>;
}
interface AuthResult {
success: boolean;
}
async function authenticate(): Promise<void> {
try {
// Connect to the user's wallet (e.g., MetaMask)
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const userAddress: string = await signer.getAddress();
// Get the current nonce from the smart contract
const authenticatorContract: AuthenticatorContract = new ethers.Contract(
authenticatorContractAddress,
authenticatorABI,
provider
) as AuthenticatorContract;
const nonce: ethers.BigNumber = await authenticatorContract.getNonce(userAddress);
// Prepare the message to be signed
const domain: ethers.TypedDataDomain = {
name: "Authenticator",
version: "1",
chainId: (await provider.getNetwork()).chainId,
verifyingContract: authenticatorContractAddress
};
const types: Record<string, Array<ethers.TypedDataField>> = {
Authenticate: [
{ name: "user", type: "address" },
{ name: "nonce", type: "uint256" }
]
};
const value: Record<string, any> = {
user: userAddress,
nonce: nonce.toNumber()
};
// Ask the user to sign the message
const signature: string = await signer._signTypedData(domain, types, value);
// Send the signature to your backend or directly to the contract
const result: AuthResult = await sendToBackendOrContract(userAddress, signature);
// Handle the authentication result
if (result.success) {
console.log("Authentication successful");
} else {
console.log("Authentication failed");
}
} catch (error) {
console.error("Authentication error:", error);
}
}
// This function needs to be implemented based on your backend or contract interaction
async function sendToBackendOrContract(userAddress: string, signature: string): Promise<AuthResult> {
// Implementation depends on your specific backend or contract interaction
// This is just a placeholder
return { success: true };
}
// These would typically be defined elsewhere in your application
const authenticatorContractAddress: string = "0x..."; // Your contract address
const authenticatorABI: ethers.ContractInterface = []; // Your contract ABI
Key points to remember:
- Always fetch the latest nonce before creating a signature.
- The nonce in the frontend should always match the nonce in the smart contract for a successful authentication.
- If an authentication attempt fails due to an outdated nonce, you should fetch the latest nonce and try again.
- Consider implementing error handling for scenarios where the nonce might be out of sync.
- In a production environment, you might want to implement a caching mechanism or state management to reduce the number of calls to the blockchain for nonce retrieval.
By following these practices, you ensure that each authentication request is unique and tied to the current state of the user's account on the blockchain, providing a secure authentication mechanism.