Sign In With Ethereum
AppKit provides a simple solution for integrating with "Sign In With Ethereum" (SIWE), a new form of authentication that enables users to control their digital identity with their Ethereum account. SIWE is a standard also known as EIP-4361.
One-Click Auth
One-Click Auth represents a key advancement within WalletConnect v2, streamlining the user authentication process in AppKit by enabling them to seamlessly connect with a wallet and sign a SIWE message with just one click.
Connecting a wallet, proving control of an address with an off-chain signature, authorizing specific actions. These are the kinds of authorizations that can be encoded as "ReCaps". ReCaps are permissions for a specific website or dapp that can be compactly encoded as a long string in the message you sign and translated by any wallet into a straight-forward one-sentence summary. WalletConnect uses permissions expressed as ReCaps to enable a One-Click Authentication.
Pre-requisites
In order for SIWE to work, you need to have a backend to communicate with. This backend will be used to generate a nonce, verify messages and handle sessions. More info here
Configure your SIWEConfig object
final _siweConfig = SIWEConfig(
getNonce: () async {
// The getNonce method functions as a safeguard
// against spoofing, akin to a CSRF token.
return await yourApi.getNonce();
},
getMessageParams: () async {
// Parameters to create the SIWE message internally.
// More info in https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-222.method
return SIWEMessageArgs(
domain: 'yourdomain.com',
uri: 'https://yourdomain.com/login',
statement: 'Please sign with your account',
methods: ['personal_sign', 'eth_sendTransaction'],
);
},
createMessage: (SIWECreateMessageArgs args) {
// Method for generating an EIP-4361-compatible message.
// You can use our provided formatMessage() method or implement your own
return SIWEUtils.formatMessage(args);
},
verifyMessage: (SIWEVerifyMessageArgs args) async {
// This function ensures the message is valid,
// has not been tampered with, and has been appropriately
// signed by the wallet address.
try {
final isValidMessage = await yourApi.verifyMessage(args.toJson());
return isValidMessage;
} catch (error) {
// error validating message
return false;
}
},
getSession: () async {
// Called after verifyMessage() succeeds
// The backend session should store the associated address and chainId
// and return it via the `getSession` method.
try {
final session = await yourApi.getSession();
return SIWESession(address: session.address, chains: [session.chainId]);
} catch (error) {
// error getting session
rethrow;
}
},
onSignIn: (SIWESession session) {
// Called after getSession() succeeds
},
signOut: () async {
// Called when wallet disconnects if `signOutOnDisconnect == true` and/or when
// `signOutOnAccountChange == true` and/or
// `signOutOnNetworkChange == true`
try {
final success = await yourApi.signOut();
return success;
} catch (error) {
// error signing out
return false;
}
},
onSignOut: () {
// Called after signOut() succeeds
},
// enabled: true, // OPTIONAL. Enables One-Click Auth + SIWE logic, if `false`, regular session proposal will be used. (default `true`)
// signOutOnDisconnect: true, // OPTIONAL (default `true`)
// signOutOnAccountChange: true, // OPTIONAL (default `true`)
// signOutOnNetworkChange: true, // OPTIONAL (default `true`)
);
Initialize ReownAppKitModal with your siweConfig
Add the siwe configuration in ReownAppKitModal
initialization
final _appKitModal = ReownAppKitModal(
context: context,
projectId: '{YOUR_PROJECT_ID}',
metadata: const PairingMetadata(
name: 'Example App',
description: 'Example app description',
url: 'https://example.com/',
icons: ['https://example.com/logo.png'],
redirect: Redirect(
native: 'exampleapp://',
universal: 'https://reown.com/exampleapp',
),
),
siweConfig: SIWEConfig(...),
);
SIWEConfig reference
class SIWEConfig {
final Future<String> Function() getNonce;
final Future<SIWEMessageArgs> Function() getMessageParams;
final String Function(SIWECreateMessageArgs args) createMessage;
final Future<bool> Function(SIWEVerifyMessageArgs args) verifyMessage;
final Future<SIWESession?> Function() getSession;
final Future<bool> Function() signOut;
// Callback when user signs in
final Function(SIWESession session)? onSignIn;
// Callback when user signs out
final VoidCallback? onSignOut;
// Defaults to true
final bool enabled;
// In milliseconds, defaults to 5 minutes
final int nonceRefetchIntervalMs;
// In milliseconds, defaults to 5 minutes
final int sessionRefetchIntervalMs;
// Defaults to true
final bool signOutOnDisconnect;
// Defaults to true
final bool signOutOnAccountChange;
// Defaults to true
final bool signOutOnNetworkChange;
//
SIWEConfig({
required this.getNonce,
required this.getMessageParams,
required this.createMessage,
required this.verifyMessage,
required this.getSession,
required this.signOut,
this.onSignIn,
this.onSignOut,
this.enabled = true,
this.signOutOnDisconnect = true,
this.signOutOnAccountChange = true,
this.signOutOnNetworkChange = true,
this.nonceRefetchIntervalMs = 300000,
this.sessionRefetchIntervalMs = 300000,
});
}
Not configuring siweConfig
object has the same effect as setting false
on siweConfig.enable
parameter.
Exported functions
generateNonce
Simple method to generate a timestamp-based nonce
SIWEUtils.generateNonce();
formatMessage
Creates EIP-4361 message based on input arguments.
SIWEUtils.formatMessage(args);
verifySignature
Verify a SIWE signature. Internally it calls your backend verification method.
await SIWEUtils.verifySignature(
address,
message,
signature,
chainId,
projectId,
);
getChainIdFromMessage
Get the chain ID from the SIWE message.
SIWEUtils.getChainIdFromMessage(message);
getAddressFromMessage
Get the address from the SIWE message.
SIWEUtils.getAddressFromMessage(message);