remit2any_auth 0.1.9 copy "remit2any_auth: ^0.1.9" to clipboard
remit2any_auth: ^0.1.9 copied to clipboard

A Flutter package for Remit2Any authentication.

Remit2Any Auth Flutter SDK #

A comprehensive Flutter package for seamless AWS Cognito authentication via optimized WebViews for Remit2Any. This package provides enhanced WebView components with advanced keyboard handling, permission management, and secure token storage.

✨ Features #

πŸ” Authentication & Security #

  • WebView-based authentication with Remit2Any Cognito integration
  • Secure token storage using flutter_secure_storage with memory caching
  • Automatic token refresh with background refresh capabilities
  • User profile management with Cognito user data fetching
  • Session management with automatic logout detection
  • Force WebView authentication option to skip token refresh
  • Logout with redirect to login for seamless re-authentication

πŸ“± Enhanced WebView Experience #

  • Advanced keyboard handling for iOS and Android
  • Smart input field scrolling to prevent keyboard overlap
  • Automatic permission requests for camera and microphone (KYC processes)
  • WhatsApp URL handling with external browser redirect
  • Multi-domain support with intelligent URL routing
  • File download capabilities with native file manager integration

πŸ›‘οΈ Robust Permission Management #

  • Dynamic permission handling for US KYC and KYC documents
  • Platform-specific optimizations for iOS and Android
  • Secure domain validation for WebView navigation
  • Cross-platform compatibility with consistent behavior

🌐 Flexible Environment Management #

  • Development and production environment support
  • Configurable URLs and settings per environment
  • Custom user agent management for WebView identification

πŸš€ Installation #

Add to your pubspec.yaml:

dependencies:
  remit2any_auth: ^0.1.8

Run:

flutter pub get

πŸ“‹ Required Permissions #

Android

Add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

iOS

Add to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access to capture documents for identity verification and KYC compliance.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access for identity verification processes and KYC compliance.</string>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

βš™οΈ Configuration #

Environment Setup #

Configure your environment in your app initialization:

import 'package:remit2any_auth/remit2any_auth.dart';

void main() {
  // Set environment (dev or prod)
  Remit2AnyEnvironmentConfig.setEnvironment(Environment.prod);
  
  // Optional: Set custom user agent suffix
  Remit2AnyEnvironmentConfig.setUserAgentSuffix('embeddablewidget');
  
  runApp(MyApp());
}

Supported Environments #

  • Development: Environment.dev β†’ https://dev.remit2any.com
  • Production: Environment.prod β†’ https://remit2any.com

πŸ“– Complete Integration Guide #

1. πŸš€ Library Setup #

First, configure your environment in your app initialization:

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

void main() {
  // Set environment based on build configuration
  Remit2AnyEnvironmentConfig.setEnvironment(Environment.dev); // or Environment.prod
  
  // Set user agent suffix for WebView identification
  Remit2AnyEnvironmentConfig.setUserAgentSuffix('embeddablewidget');
  
  runApp(MyApp());
}

2. πŸ” Sign In Authentication #

Use getAccessTokenWithAuth() for sign-in as it handles authentication automatically. You can optionally provide an email to pre-fill the authentication form:

import 'package:remit2any_auth/remit2any_auth.dart';

class AuthService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Sign in using getAccessTokenWithAuth (Recommended)
  /// This method automatically handles authentication if needed
  Future<String?> signIn(BuildContext context, {String? email, bool forceWebView = false}) async {
    try {
      final accessToken = await _auth.getAccessTokenWithAuth(context, email: email, forceWebView: forceWebView);
      
      if (accessToken != null) {
        print('βœ… Successfully signed in');
        print('Access token length: ${accessToken.length}');
        return accessToken;
      } else {
        print('❌ User cancelled authentication');
        return null;
      }
    } catch (e) {
      print('❌ Sign-in error: $e');
      return null;
    }
  }

}

πŸ“§ Email Parameter Support

The getAccessTokenWithAuth() method now supports an optional email parameter to improve user experience:

class EmailAuthExample {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Sign in without email (existing behavior)
  Future<String?> signInWithoutEmail(BuildContext context) async {
    return await _auth.getAccessTokenWithAuth(context);
  }

  /// Sign in with email pre-fill (new feature)
  Future<String?> signInWithEmail(BuildContext context, String email) async {
    return await _auth.getAccessTokenWithAuth(context, email: email);
  }

  /// Example: Sign in with user's stored email
  Future<String?> signInWithStoredEmail(BuildContext context) async {
    // Get stored email from previous session
    final storedEmail = await _auth.getStoredEmail();
    
    if (storedEmail != null && storedEmail.isNotEmpty) {
      print('πŸ“§ Using stored email: $storedEmail');
      return await _auth.getAccessTokenWithAuth(context, email: storedEmail);
    } else {
      print('πŸ“§ No stored email, signing in without pre-fill');
      return await _auth.getAccessTokenWithAuth(context);
    }
  }
}

Benefits of Email Parameter:

  • βœ… Pre-fills email field in the authentication form
  • βœ… Faster user experience - users don't need to type their email again
  • βœ… Reduces typos in email entry
  • βœ… Seamless re-authentication using stored email from previous sessions

πŸš€ Force WebView Authentication

The getAccessTokenWithAuth() method now supports a forceWebView parameter to skip token refresh and go directly to WebView authentication:

class ForceWebViewExample {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Force WebView authentication (skip token refresh)
  Future<String?> forceWebViewAuth(BuildContext context, {String? email}) async {
    return await _auth.getAccessTokenWithAuth(
      context, 
      email: email, 
      forceWebView: true  // Skip token refresh, go directly to WebView
    );
  }

  /// Normal authentication (try token refresh first)
  Future<String?> normalAuth(BuildContext context, {String? email}) async {
    return await _auth.getAccessTokenWithAuth(
      context, 
      email: email, 
      forceWebView: false  // Default behavior - try token refresh first
    );
  }
}

When to use forceWebView: true:

  • βœ… Fresh authentication - when you want to ensure a clean login
  • βœ… Token issues - when existing tokens are corrupted or invalid
  • βœ… Security requirements - when you need to force re-authentication
  • βœ… Testing scenarios - when you want to test the full login flow

πŸ”„ Logout with Redirect to Login

The signOut() method now supports automatic redirect to login after logout:

class LogoutExample {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Normal logout (existing behavior)
  Future<void> normalLogout(BuildContext context) async {
    await _auth.signOut(context);
  }

  /// Logout with automatic redirect to login
  Future<void> logoutWithRedirect(BuildContext context) async {
    await _auth.signOut(context, redirectToLogin: true);
  }
}

Benefits of redirectToLogin: true:

  • βœ… Seamless user experience - no need to manually trigger login after logout
  • βœ… Automatic re-authentication - user can immediately sign in again
  • βœ… Reduced friction - eliminates the need for separate login calls
  • βœ… Consistent flow - maintains authentication state automatically

3. πŸ”‘ Get Access Token #

Always check for existing tokens before requiring re-authentication:

class TokenManager {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Get access token without UI (context-free)
  Future<String?> getAccessToken() async {
    try {
      final accessToken = await _auth.getAccessToken();
      
      if (accessToken != null) {
        print('βœ… Valid access token found');
        return accessToken;
      } else {
        print('❌ No valid access token - authentication required');
        return null;
      }
    } catch (e) {
      print('Error getting access token: $e');
      return null;
    }
  }

  /// Complete token flow with automatic re-authentication
  Future<String?> getAccessTokenWithFallback(BuildContext context, {String? email}) async {
    // Step 1: Try to get existing token
    String? token = await getAccessToken();
    
    // Step 2: If null, authenticate user
    if (token == null) {
      print('πŸ”„ Access token is null, initiating sign-in...');
      token = await _auth.getAccessTokenWithAuth(context, email: email);
    }
    
    return token;
  }
}

4. ⚠️ Handle Null Access Token #

Important: If getAccessToken() returns null, you MUST call sign-in again:

class ApiService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  Future<void> makeApiCall(BuildContext context, {String? email}) async {
    // Get access token
    String? accessToken = await _auth.getAccessToken();
    
    // Check if token is null
    if (accessToken == null) {
      print('🚨 Access token is null - user needs to sign in');
      
      // Trigger sign-in process with optional email
      accessToken = await _auth.getAccessTokenWithAuth(context, email: email);
      
      if (accessToken == null) {
        print('❌ User cancelled sign-in');
        return; // Exit if user cancels
      }
    }
    
    // Now use the access token for API calls
    await _callApiWithToken(accessToken);
  }

  Future<void> _callApiWithToken(String accessToken) async {
    // Your API call implementation
    print('🌐 Making API call with token: ${accessToken.substring(0, 20)}...');
  }
}

5. 🌐 Open WebViews #

All webview methods return a result map that you should check for logout status:

class WebViewService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Open full site with custom tenant
  Future<void> openFullSite(BuildContext context, {String? tenant}) async {
    final result = await _auth.openFullSite(context, tenant: tenant);
    _handleWebViewResult(context, result, 'Full Site');
  }

  /// Open transfer page
  Future<void> openTransfer(BuildContext context, {double usdAmount = 100.0}) async {
    final result = await _auth.openTransfer(context, usdAmount: usdAmount);
    _handleWebViewResult(context, result, 'Transfer');
  }

  /// Open wallet deposit
  Future<void> openWalletDeposit(BuildContext context) async {
    final result = await _auth.openWalletDeposit(context);
    _handleWebViewResult(context, result, 'Wallet Deposit');
  }

  /// Open transactions
  Future<void> openTransactions(BuildContext context) async {
    final result = await _auth.openTransactions(context);
    _handleWebViewResult(context, result, 'Transactions');
  }

  /// Open US KYC documents
  Future<void> openUsKyc(BuildContext context) async {
    final result = await _auth.openUsKyc(context);
    _handleWebViewResult(context, result, 'US KYC');
  }

  /// Open Indian KYC documents
  Future<void> openIndianKyc(BuildContext context) async {
    final result = await _auth.openIndianKyc(context);
    _handleWebViewResult(context, result, 'Indian KYC');
  }

  /// Open bank accounts
  Future<void> openBankAccounts(BuildContext context) async {
    final result = await _auth.openBankAccounts(context);
    _handleWebViewResult(context, result, 'Bank Accounts');
  }

  /// Open US-US transfers
  Future<void> openUsUsTransfers(BuildContext context) async {
    final result = await _auth.openUsUsTransfers(context);
    _handleWebViewResult(context, result, 'US-US Transfers');
  }

  /// Open wallet
  Future<void> openWallet(BuildContext context) async {
    final result = await _auth.openWallet(context);
    _handleWebViewResult(context, result, 'Wallet');
  }

  /// Open wallet withdrawal
  Future<void> openWalletWithdrawal(BuildContext context) async {
    final result = await _auth.openWalletWithdrawal(context);
    _handleWebViewResult(context, result, 'Wallet Withdrawal');
  }

  /// Handle webview results and check for logout
  void _handleWebViewResult(BuildContext context, Map<String, dynamic>? result, String pageName) {
    if (result != null) {
      print('$pageName result: $result');
      
      // Check for logout status
      if (result['loggedOut'] == true) {
        print('🚨 User was logged out from $pageName');
        _handleLogout(context);
      } else if (result['closed'] == true) {
        print('πŸ“± User closed $pageName webview');
      }
    } else {
      print('$pageName: No result returned');
    }
  }

  /// Handle logout from webview
  Future<void> _handleLogout(BuildContext context) async {
    print('πŸ”„ User logged out from webview, initiating re-authentication...');
    
    // NOTE: signOut(context) is ALREADY called internally by the library
    // when loggedOut: true is detected. Just handle re-authentication.
    
    // Trigger sign-in process
    final accessToken = await _auth.getAccessTokenWithAuth(context);
    
    if (accessToken != null) {
      print('βœ… Re-authentication successful');
    } else {
      print('❌ Re-authentication cancelled by user');
    }
  }
}

6. 🚨 Critical: Handle WebView Logout #

Important: When any webview returns loggedOut: true, the library automatically calls signOut(context) internally, but you still need to handle re-authentication:

class WebViewHandler {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  Future<void> openAnyWebView(BuildContext context) async {
    // Example: Opening transfer page
    final result = await _auth.openTransfer(context, usdAmount: 100.0);
    
    // CRITICAL: Always check for logout
    if (result != null && result['loggedOut'] == true) {
      print('🚨 CRITICAL: User was logged out from webview!');
      
      // NOTE: signOut(context) is ALREADY called internally by the library
      // You only need to handle re-authentication
      await _handleReAuthentication(context);
    }
  }

  Future<void> _handleReAuthentication(BuildContext context) async {
    print('πŸ”„ Re-authenticating after webview logout...');
    
    try {
      // The library already cleared tokens, just re-authenticate
      final accessToken = await _auth.getAccessTokenWithAuth(context);
      
      if (accessToken != null) {
        print('βœ… Re-authentication successful after webview logout');
        // Continue with your app flow
      } else {
        print('❌ Re-authentication cancelled by user');
        // Handle failed re-authentication (maybe redirect to login screen)
      }
    } catch (e) {
      print('❌ Re-authentication error: $e');
    }
  }
}

What Happens Internally When loggedOut: true

The library automatically performs these cleanup steps:

  1. Calls signOut(context) which:
    • Clears all stored tokens (TokenStorage.clearTokens())
    • Cancels token refresh timers
    • Opens logout WebView to clear Cognito session cookies
  2. Logs the event with debug information
  3. Returns the result with loggedOut: true

You only need to handle the re-authentication part!

7. πŸ”„ Complete Authentication Flow Example #

Here's a complete example showing the recommended authentication pattern:

class CompleteAuthFlow {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Complete authentication and API flow
  Future<void> performAuthenticatedAction(BuildContext context) async {
    try {
      // Step 1: Get access token (try existing first)
      String? accessToken = await _auth.getAccessToken();
      
      // Step 2: If null, authenticate
      if (accessToken == null) {
        print('πŸ”‘ No access token found, authenticating...');
        accessToken = await _auth.getAccessTokenWithAuth(context);
        
        if (accessToken == null) {
          print('❌ Authentication cancelled');
          return;
        }
      }
      
      // Step 3: Use access token for API calls
      await _makeApiCall(accessToken);
      
      // Step 4: Open webview and handle potential logout
      await _openWebViewWithLogoutHandling(context);
      
    } catch (e) {
      print('❌ Authentication flow error: $e');
    }
  }

  Future<void> _makeApiCall(String accessToken) async {
    print('🌐 Making API call with token...');
    // Your API implementation here
  }

  Future<void> _openWebViewWithLogoutHandling(BuildContext context) async {
    final result = await _auth.openWallet(context);
    
    // Handle webview result
    if (result != null) {
      if (result['loggedOut'] == true) {
        print('🚨 Webview logout detected, re-authenticating...');
        
        // NOTE: Library already called signOut(context) internally
        // Just handle re-authentication
        final newToken = await _auth.getAccessTokenWithAuth(context);
        
        if (newToken != null) {
          print('βœ… Re-authentication successful');
        }
      }
    }
  }
}

8. πŸ› οΈ Additional Utility Methods #

class AuthUtilities {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Get stored user information
  Future<Map<String, String?>> getStoredUserData() async {
    final email = await _auth.getStoredEmail();
    final userId = await _auth.getStoredUserId();
    
    return {
      'email': email,
      'userId': userId,
    };
  }

  /// Check US KYC completion status
  Future<bool> checkUsKycStatus() async {
    try {
      return await _auth.isUsKycCompleted();
    } catch (e) {
      print('Error checking US KYC status: $e');
      return false;
    }
  }

  /// Check India KYC completion status
  Future<bool> checkIndiaKycStatus() async {
    try {
      return await _auth.isIndiaKycCompleted();
    } catch (e) {
      print('Error checking India KYC status: $e');
      return false;
    }
  }

  /// Complete sign out
  Future<void> signOut(BuildContext context) async {
    try {
      await _auth.signOut(context);
      print('βœ… Successfully signed out');
    } catch (e) {
      print('❌ Sign out error: $e');
    }
  }
}

9. πŸ“‹ Quick Reference #

Essential Methods Summary

// 1. Setup (in main.dart)
Remit2AnyEnvironmentConfig.setEnvironment(Environment.dev);
Remit2AnyEnvironmentConfig.setUserAgentSuffix('yourApp');

// 2. Sign In (with optional email and force WebView)
final accessToken = await auth.getAccessTokenWithAuth(context, email: "user@example.com", forceWebView: false);

// 3. Get Access Token (check for null!)
final token = await auth.getAccessToken();
if (token == null) {
  // Must sign in again (with optional email and force WebView)
  await auth.getAccessTokenWithAuth(context, email: "user@example.com", forceWebView: false);
}

// 4. Open WebViews (check for logout!)
final result = await auth.openTransfer(context);
if (result?['loggedOut'] == true) {
  // Must sign in again (with optional email and force WebView)
  await auth.getAccessTokenWithAuth(context, email: "user@example.com", forceWebView: false);
}

// 5. Sign Out (with optional redirect to login)
await auth.signOut(context, redirectToLogin: false);

Available WebView Methods

All methods return Future<Map<String, dynamic>?> and should be checked for loggedOut: true:

  • openFullSite(context, {tenant}) - Complete website
  • openTransfer(context, {usdAmount}) - Transfer page
  • openWalletDeposit(context) - Wallet deposit
  • openWalletWithdrawal(context) - Wallet withdrawal
  • openTransactions(context) - Transaction history
  • openUsKyc(context) - US KYC documents
  • openIndianKyc(context) - Indian KYC documents
  • openBankAccounts(context) - Bank account management
  • openUsUsTransfers(context) - US to US transfers
  • openWallet(context) - Wallet overview

🎯 Key Features Explained #

Enhanced Keyboard Handling #

The package includes advanced keyboard handling that automatically:

  • βœ… Prevents input fields from being hidden behind the keyboard on iOS
  • βœ… Maintains Android's existing smooth keyboard behavior
  • βœ… Automatically scrolls focused input fields into view
  • βœ… Handles viewport adjustments for optimal user experience

Intelligent Permission Management #

Permissions are requested dynamically based on URL patterns:

  • πŸŽ₯ Camera & Microphone: Automatically requested for URLs containing uskyc or kyc-documents
  • πŸ”’ Secure Domains: Only trusted Remit2Any domains are allowed
  • 🌐 External URLs: WhatsApp links and other external URLs open in system browser

Smart WebView Navigation #

  • πŸ“± Platform Optimization: Different behaviors optimized for iOS and Android
  • πŸ”„ User Agent Management: Dynamic user agent switching for different services (Google OAuth, etc.)
  • πŸ“ File Downloads: Native file download handling with system integration
  • πŸ” Security: SSL certificate validation with custom handling

πŸ› οΈ Advanced Configuration #

Environment Variables #

// Check current environment
if (Remit2AnyEnvironmentConfig.isDev) {
  print('Development mode');
} else {
  print('Production mode');
}

// Get environment-specific URLs
print('Auth URL: ${Remit2AnyEnvironmentConfig.authUrl}');
print('Base URL: ${Remit2AnyEnvironmentConfig.baseUrl}');
print('User Agent Suffix: ${Remit2AnyEnvironmentConfig.userAgentSuffix}');

## πŸ”§ Dependencies

This package depends on:
- **flutter_inappwebview**: ^6.0.0 - Advanced WebView functionality
- **flutter_secure_storage**: ^9.0.0 - Secure token storage
- **http**: ^1.0.0 - HTTP requests for API calls
- **permission_handler**: ^11.0.0 - Runtime permission management
- **url_launcher**: ^6.1.4 - External URL handling
- **path_provider**: ^2.1.1 - File system access for downloads

## πŸ› Troubleshooting

### Common Issues

πŸ“š API Reference #

Core Classes #

Remit2AnyAuth

Main authentication service class.

Methods:

  • signIn(BuildContext context, {String? email, bool forceWebView = false}) β†’ Future<Map<String, dynamic>?>
  • signOut(BuildContext context, {bool redirectToLogin = false}) β†’ Future<void>
  • getAccessToken() β†’ Future<String?>
  • getAccessTokenWithAuth(BuildContext context, {String? email, bool forceWebView = false}) β†’ Future<String?>
  • openFullSite(BuildContext context, {String? tenant}) β†’ Future<Map<String, dynamic>?>
  • openUsKyc(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openTransfer(BuildContext context, {double? usdAmount}) β†’ Future<Map<String, dynamic>?>
  • openWalletDeposit(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openWalletWithdrawal(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openTransactions(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • isUsKycCompleted() β†’ Future<bool>
  • isIndiaKycCompleted() β†’ Future<bool>
  • getStoredEmail() β†’ Future<String?>
  • getStoredUserId() β†’ Future<String?>

Remit2AnyEnvironmentConfig

Environment configuration management.

Methods:

  • setEnvironment(Environment environment) β†’ void
  • setUserAgentSuffix(String suffix) β†’ void

Properties:

  • environment β†’ Environment
  • authUrl β†’ String
  • baseUrl β†’ String
  • userAgentSuffix β†’ String
  • isDev β†’ bool

πŸ”„ Migration Guide #

From v0.1.6 to v0.1.8 #

  • NEW: Added forceWebView parameter to getAccessTokenWithAuth() and signIn()
  • NEW: Added redirectToLogin parameter to signOut()
  • NEW: Memory caching for token storage (faster performance)
  • NEW: Centralized authentication handler to eliminate code duplication
  • IMPROVED: Enhanced logout flow with automatic redirect to login
  • IMPROVED: Better error handling and logging

From v0.1.0 to v0.1.6 #

  • Enhanced keyboard handling for iOS
  • Improved permission management
  • Added WhatsApp URL handling
  • Enhanced WebView components

No breaking changes - update your pubspec.yaml version and run flutter pub get.

Development Setup #

git clone https://github.com/your-repo/remit2any_auth.git
cd remit2any_auth
flutter pub get
cd example
flutter pub get
flutter run

πŸ“„ License #

This project is licensed under the MIT License - see the LICENSE file for details.

Made with ❀️ by the Remit2Any Team

0
likes
95
points
347
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for Remit2Any authentication.

Documentation

API reference

License

unknown (license)

Dependencies

flutter, flutter_inappwebview, flutter_secure_storage, http, path_provider, permission_handler, url_launcher

More

Packages that depend on remit2any_auth