Para supports social login authentication in Flutter applications, allowing users to sign in using popular providers like Google, Apple, Twitter, Discord, and Facebook. This guide explains how to implement social login in your Flutter app using Para’s Flutter SDK v2.

The social login flow in mobile applications requires handling browser sessions and deeplinks to redirect users back to your app after successful authentication. Para’s v2 API provides a streamlined flow that automatically handles both new and existing users.

Prerequisites

Before implementing social login, ensure you have completed the basic Para setup for your Flutter application.

Flutter Setup

Configuration

First, set up URL schemes to handle OAuth callbacks:

Add URL schemes to your Info.plist file to handle callbacks from authentication:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourapp</string>
    </array>
  </dict>
</array>

The URL scheme should match the appScheme parameter you used when initializing Para (e.g., yourapp://callback).

Implementation

V2 OAuth Authentication

Para’s v2 API provides a streamlined OAuth flow that handles both new and existing users:

import 'package:para/para.dart';

Future<void> authenticateWithOAuth(OAuthMethod provider) async {
  try {
    // Handle OAuth flow - this manages the entire process
    final authState = await para.verifyOAuth(
      provider: provider,
      appScheme: 'yourapp://callback', // Your app's deep link scheme
    );
    
    // Handle the result based on auth stage
    switch (authState.stage) {
      case AuthStage.login:
        // Existing user - login with passkey
        final walletFuture = para.loginWithPasskey(
          email: authState.email,
        );
        final wallet = await walletFuture.future;
        print('User logged in: ${wallet.address}');
        break;
        
      case AuthStage.signup:
        // New user - create wallet
        final walletFuture = para.createWallet(
          type: WalletType.evm,
          skipDistribute: false,
        );
        final wallet = await walletFuture.future;
        print('New wallet created: ${wallet.address}');
        break;
        
      default:
        print('Unexpected auth stage: ${authState.stage}');
    }
  } catch (e) {
    print('OAuth authentication failed: $e');
  }
}

Individual OAuth Providers

Future<void> loginWithGoogle() async {
  await authenticateWithOAuth(OAuthMethod.google);
}

Farcaster Authentication

Farcaster authentication follows a unique flow in V2:

Future<void> authenticateWithFarcaster() async {
  // Step 1: Get the Farcaster connect URI
  final connectUri = await para.getFarcasterConnectUri().future;
  
  // Step 2: Launch the URI (opens Farcaster app/website for user authorization)
  if (await canLaunchUrl(Uri.parse(connectUri))) {
    await launchUrl(Uri.parse(connectUri));
  }
  
  // Step 3: Poll for verification result
  final authState = await para.verifyFarcaster().future;
  
  // Step 4: Handle the authentication state
  switch (authState.stage) {
    case AuthStage.login:
      // Existing user - open passkey URL and wait for login
      if (authState.passkeyUrl != null) {
        final webAuthSession = FlutterWebAuthSession(
          callbackUrlScheme: 'yourapp',
        );
        
        await launchUrl(Uri.parse(authState.passkeyUrl!));
        
        // Wait for login to complete
        final loginResult = await para.waitForLogin().future;
        print('Farcaster user logged in');
      }
      break;
      
    case AuthStage.signup:
      // New user - create wallet
      if (authState.passkeyUrl != null) {
        final webAuthSession = FlutterWebAuthSession(
          callbackUrlScheme: 'yourapp',
        );
        
        await launchUrl(Uri.parse(authState.passkeyUrl!));
        
        // Wait for wallet creation
        final walletResult = await para.waitForWalletCreation().future;
        print('New Farcaster wallet created');
      }
      break;
      
    case AuthStage.verify:
      // Should not occur for Farcaster
      print('Unexpected verify stage for Farcaster');
      break;
  }
}

Available OAuth Providers

Para supports the following OAuth providers:

ProviderEnum ValueDescription
GoogleOAuthMethod.googleGoogle authentication
AppleOAuthMethod.appleApple Sign-In
TwitterOAuthMethod.twitterTwitter (X.com) authentication
DiscordOAuthMethod.discordDiscord authentication
FacebookOAuthMethod.facebookFacebook authentication

Complete Social Login Widget

Here’s a complete example of a social login widget:

import 'package:flutter/material.dart';
import 'package:para/para.dart';

class SocialLoginWidget extends StatefulWidget {
  @override
  _SocialLoginWidgetState createState() => _SocialLoginWidgetState();
}

class _SocialLoginWidgetState extends State<SocialLoginWidget> {
  bool _isLoading = false;

  Future<void> _handleOAuthLogin(OAuthMethod provider) async {
    if (_isLoading) return;
    
    setState(() => _isLoading = true);
    
    try {
      // Use the unified OAuth flow for v2
      final authState = await para.verifyOAuth(
        provider: provider,
        appScheme: 'yourapp://callback',
      );
      
      // OAuth authentication handles both new and existing users
      final isActive = await para.isSessionActive();
      if (isActive) {
        _navigateToHome();
        return;
      }
      
    } catch (e) {
      _showError('Authentication failed: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _handleFarcasterLogin() async {
    if (_isLoading) return;
    
    setState(() => _isLoading = true);
    
    try {
      // Step 1: Get the Farcaster connect URI
      final connectUri = await para.getFarcasterConnectUri().future;
      
      // Step 2: Launch the URI
      if (await canLaunchUrl(Uri.parse(connectUri))) {
        await launchUrl(Uri.parse(connectUri));
      }
      
      // Step 3: Poll for verification result
      final authState = await para.verifyFarcaster().future;
      
      // Step 4: Handle the authentication state
      switch (authState.stage) {
        case AuthStage.login:
        case AuthStage.signup:
          if (authState.passkeyUrl != null) {
            await launchUrl(Uri.parse(authState.passkeyUrl!));
            
            // Wait for completion
            if (authState.stage == AuthStage.login) {
              await para.waitForLogin().future;
            } else {
              await para.waitForWalletCreation().future;
            }
          }
          break;
        case AuthStage.verify:
          // Should not occur for Farcaster
          break;
      }
      
      _navigateToHome();
      
    } catch (e) {
      _showError('Farcaster authentication failed: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _SocialButton(
          onPressed: () => _handleOAuthLogin(OAuthMethod.google),
          icon: Icons.login,
          label: 'Continue with Google',
          isLoading: _isLoading,
        ),
        SizedBox(height: 12),
        _SocialButton(
          onPressed: () => _handleOAuthLogin(OAuthMethod.apple),
          icon: Icons.apple,
          label: 'Continue with Apple',
          isLoading: _isLoading,
        ),
        SizedBox(height: 12),
        _SocialButton(
          onPressed: () => _handleOAuthLogin(OAuthMethod.twitter),
          icon: Icons.alternate_email,
          label: 'Continue with Twitter',
          isLoading: _isLoading,
        ),
        SizedBox(height: 12),
        _SocialButton(
          onPressed: () => _handleFarcasterLogin(),
          icon: Icons.forum,
          label: 'Continue with Farcaster',
          isLoading: _isLoading,
        ),
      ],
    );
  }

  void _navigateToHome() {
    // Navigate to your app's main screen
    Navigator.pushReplacementNamed(context, '/home');
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }
}

class _SocialButton extends StatelessWidget {
  final VoidCallback onPressed;
  final IconData icon;
  final String label;
  final bool isLoading;

  const _SocialButton({
    required this.onPressed,
    required this.icon,
    required this.label,
    required this.isLoading,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: ElevatedButton.icon(
        onPressed: isLoading ? null : onPressed,
        icon: isLoading 
          ? SizedBox(
              width: 20,
              height: 20,
              child: CircularProgressIndicator(strokeWidth: 2),
            )
          : Icon(icon),
        label: Text(label),
        style: ElevatedButton.styleFrom(
          padding: EdgeInsets.symmetric(vertical: 12),
        ),
      ),
    );
  }
}

Session Management

Check if a user is already authenticated when your app starts:

Future<void> checkAuthStatus() async {
  final isActive = await para.isSessionActive();
  
  if (isActive) {
    // User is logged in
    final authDetails = await para.getCurrentUserAuthDetails();
    final wallets = await para.fetchWallets();
    
    print('User is logged in with ${wallets.length} wallets');
    
    // Navigate to main app
    Navigator.pushReplacementNamed(context, '/home');
  } else {
    // Show login screen
    Navigator.pushReplacementNamed(context, '/login');
  }
}

Social login with Para still requires creating a native passkey to secure the user’s wallets. After social authentication completes, Para associates a native passkey with the user’s account. For returning users, the native passkey is used for authentication. The passkey is associated on a per-app basis, making authentication streamlined.

Error Handling

Handle common OAuth errors:

Future<void> handleOAuthWithErrorHandling(OAuthMethod provider) async {
  try {
    await authenticateWithOAuth(provider);
  } catch (e) {
    if (e.toString().contains('cancelled')) {
      // User cancelled the OAuth flow
      print('Authentication cancelled by user');
    } else if (e.toString().contains('network')) {
      // Network error
      print('Network error during authentication');
    } else {
      // Other errors
      print('Authentication error: $e');
    }
  }
}

Examples

Explore our complete example implementations for social login with Para:

Next Steps

After integrating Para, you can explore other features and integrations to enhance your Para experience.