This example demonstrates how to use Grouped Connections (formerly Aggregate Verifiers) in a Flutter application. Grouped Connections allow users to log in through different authentication methods while maintaining the same wallet address, providing a seamless multi-provider authentication experience.
- Multi-Provider Authentication: Google, Auth0, Email Passwordless, and more
- Single Wallet Address: Same wallet regardless of login method used
- Flexible Identity: Users can choose their preferred login method
- EVM Wallet: Automatic Ethereum wallet creation linked across providers
- Blockchain Interactions: Full blockchain operations using web3dart
- Secure Key Management: Non-custodial key management with consistent identity
- Cross-Platform: Single codebase for iOS and Android
Grouped Connections link multiple authentication methods together so users get the same wallet address regardless of how they log in:
- User logs in with Google → Gets wallet
0xABC... - Same user logs in with Email Passwordless → Gets same wallet
0xABC... - Without grouping, each method would create a different wallet!
Use Case: Your users want flexibility - some prefer Google, others prefer email. With Grouped Connections, they get one wallet they can access via any method.
- Flutter: 3.0.0 or higher
- Dart: 2.18.0 or higher
- MetaMask Embedded Wallets: Dashboard account
- Auth0 Account (optional, for custom provider): Create one here
- iOS (for iOS development):
- iOS 14+, Xcode 12+, Swift 5.x, CocoaPods
- Android (for Android development):
- API level 26+, compileSdkVersion 34, JDK 11+
-
Clone the repository:
git clone https://github.com/Web3Auth/web3auth-flutter-examples.git cd web3auth-flutter-examples/flutter-aggregate-verifier-example -
Install dependencies:
flutter pub get
-
iOS Setup (for iOS development):
cd ios && pod install && cd ..
-
Create a project on the Embedded Wallets Dashboard
-
Choose your network:
- Sapphire Devnet: For development/testing
- Sapphire Mainnet: For production
-
Create a Grouped Connection:
- Go to "Auth" → "Grouped Connections"
- Click "Create Grouped Connection"
- Give it a name (e.g.,
flutter-grouped-auth)
Add the authentication methods you want to group together:
Example 1: Google + Email Passwordless
-
Add Google sub-connection:
- Provider: Google
- User ID Field:
email - This uses the user's email as the common identifier
-
Add Email Passwordless sub-connection:
- Provider: Email Passwordless
- User ID Field:
email - Same email = same wallet!
Example 2: Custom Auth0 + Google
-
Add Auth0 sub-connection:
- Provider: Custom (Auth0)
- Configure Auth0 domain and client ID
- User ID Field:
email
-
Add Google sub-connection:
- Provider: Google
- User ID Field:
email
Critical: The User ID Field must be the same across all sub-connections and must contain the same value for the same user. For example, email works for Google + Email Passwordless because both use the user's email address.
- iOS: Allowlist
{bundleId}://auth - Android: Allowlist your package name
Update the configuration in lib/main.dart:
import 'package:web3auth_flutter/web3auth_flutter.dart';
import 'dart:io';
Future<void> initWeb3Auth() async {
late final Uri redirectUrl;
if (Platform.isAndroid) {
redirectUrl = Uri.parse('w3a://com.example.aggregateapp/auth');
} else if (Platform.isIOS) {
redirectUrl = Uri.parse('com.example.aggregateapp://auth');
}
await Web3AuthFlutter.init(
Web3AuthOptions(
clientId: "YOUR_WEB3AUTH_CLIENT_ID",
network: Network.sapphire_mainnet,
redirectUrl: redirectUrl,
)
);
await Web3AuthFlutter.initialize();
}# Run in debug mode
flutter run
# Build for release
flutter build ios # For iOS
flutter build apk # For Androidlib/
├── main.dart # Entry point & initialization
├── services/
│ ├── web3auth_service.dart # Web3Auth operations
│ └── blockchain.dart # Blockchain operations
└── screens/
├── home.dart # Home screen with wallet info
└── login.dart # Login screen with multiple options
Future<void> loginWithGoogle() async {
try {
final Web3AuthResponse response = await Web3AuthFlutter.login(
LoginParams(
loginProvider: Provider.google,
mfaLevel: MFALevel.NONE,
)
);
print('Logged in with Google');
print('Wallet address: ${response.userInfo?.publicAddress}');
} catch (e) {
print('Login error: $e');
}
}Future<void> loginWithEmailPasswordless(String email) async {
try {
final Web3AuthResponse response = await Web3AuthFlutter.login(
LoginParams(
loginProvider: Provider.email_passwordless,
extraLoginOptions: ExtraLoginOptions(
login_hint: email, // User's email address
),
)
);
print('Logged in with Email Passwordless');
print('Wallet address: ${response.userInfo?.publicAddress}');
// This will be the SAME address as Google login if using same email!
} catch (e) {
print('Login error: $e');
}
}import 'package:auth0_flutter/auth0_flutter.dart';
Future<void> loginWithAuth0() async {
try {
// Step 1: Login with Auth0
final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID');
final credentials = await auth0.webAuthentication().login();
// Step 2: Get ID Token
final idToken = credentials.idToken;
// Step 3: Login to Web3Auth with JWT
final Web3AuthResponse response = await Web3AuthFlutter.login(
LoginParams(
loginProvider: Provider.jwt,
extraLoginOptions: ExtraLoginOptions(
id_token: idToken,
verifierIdField: 'email', // Must match grouped connection config
),
)
);
print('Logged in with Auth0');
print('Wallet address: ${response.userInfo?.publicAddress}');
// Same address as Google/Email if using same email!
} catch (e) {
print('Login error: $e');
}
}import 'package:web3dart/web3dart.dart';
Future<void> getWalletInfo() async {
// Get private key from Web3Auth
final privateKey = await Web3AuthFlutter.getPrivKey();
// Create credentials
final credentials = EthPrivateKey.fromHex(privateKey);
// Get address
final address = credentials.address;
print('Wallet address: ${address.hex}');
// This address will be the same regardless of login method!
// Get balance
final client = Web3Client('YOUR_RPC_URL', Client());
final balance = await client.getBalance(address);
print('Balance: ${balance.getValueInUnit(EtherUnit.ether)} ETH');
}Future<void> logout() async {
await Web3AuthFlutter.logout();
}- Consistent Identity: User ID field must be the same across all providers
- Email Verification: For email-based grouping, ensure emails are verified
- Non-Custodial: Private keys derived using Shamir Secret Sharing
- Network Consistency: Never change Client ID or grouped connection config in production
- User ID Field Selection: Choose a field that uniquely identifies users across all providers
- Provider Compatibility: Ensure all grouped providers return the same user identifier
The User ID Field must:
- Exist in the JWT/OAuth response from all sub-connections
- Contain the same value for the same user across providers
- Be unique per user
Good combinations:
- Google (
email) + Email Passwordless (email) - Auth0 Google (
email) + Auth0 Facebook (email) - Two custom JWT providers with same email field
Bad combinations:
- Google (
sub) + Email Passwordless (email) - Different field values! - Twitter (
user_id) + Google (sub) - Different identifiers!
-
Test with same user:
Login with Google (user@example.com) → Note wallet address Logout Login with Email Passwordless (user@example.com) → Verify SAME address -
Test with different users:
Login with Google (user1@example.com) → Note wallet address Logout Login with Email Passwordless (user2@example.com) → Verify DIFFERENT address
Problem: Same user gets different wallet addresses with different login methods
Solutions:
- Verify Grouped Connection is properly configured in dashboard
- Check that User ID Field is identical across all sub-connections
- Ensure the field value (e.g., email) is actually the same for the user
- Verify you're using the same Client ID
- Check network (devnet/mainnet) hasn't changed
Problem: One of the login methods fails
Solutions:
- Test each sub-connection independently first
- Verify the sub-connection configuration in dashboard
- For JWT/custom auth: Ensure JWT contains the User ID Field
- Check that all OAuth providers are properly configured
Problem: Error about missing verifier ID field
Solutions:
- Check the JWT token to see what fields are actually present
- Verify the User ID Field name matches exactly (case-sensitive)
- For Google: Use
emailorsub - For Auth0: Use
suboremaildepending on configuration
- MetaMask Embedded Wallets Docs
- Flutter SDK Reference
- Grouped Connections Guide
- Custom Authentication
Need help? Reach out through:
This example is available under the MIT License. See the LICENSE file for more info.