remit2any_auth 0.1.9
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:
- Calls
signOut(context)
which:- Clears all stored tokens (
TokenStorage.clearTokens()
) - Cancels token refresh timers
- Opens logout WebView to clear Cognito session cookies
- Clears all stored tokens (
- Logs the event with debug information
- 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 websiteopenTransfer(context, {usdAmount})
- Transfer pageopenWalletDeposit(context)
- Wallet depositopenWalletWithdrawal(context)
- Wallet withdrawalopenTransactions(context)
- Transaction historyopenUsKyc(context)
- US KYC documentsopenIndianKyc(context)
- Indian KYC documentsopenBankAccounts(context)
- Bank account managementopenUsUsTransfers(context)
- US to US transfersopenWallet(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
orkyc-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 togetAccessTokenWithAuth()
andsignIn()
- NEW: Added
redirectToLogin
parameter tosignOut()
- 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