Flutter CIA SDK
A complete authentication SDK for Flutter applications using the CIA authentication system. Features secure token storage, automatic token refresh, and white-label UI support.
Features
- π Secure Token Storage - Uses iOS Keychain and Android EncryptedSharedPreferences
- π Auto Token Refresh - Automatically refreshes expired access tokens
- π± Email & Phone Login - Support for both authentication methods
- π’ Organization Federated Auth - Connect to organization-specific servers
- π¨ White-Label UI - Customizable branding for login/register screens
- β‘ Never Logout - Persistent sessions with secure refresh tokens
- π Middleware - Route protection and auth state management
Installation
Add to your pubspec.yaml:
dependencies:
flutter_cia:
path: ../flutter_cia # Or publish to pub.flutter-io.cn
Quick Start
1. Setup Provider
Wrap your app with CIAAuthProvider:
import 'package:flutter_cia/flutter_cia.dart';
void main() {
runApp(
CIAAuthProvider(
baseUrl: 'https://cia.mn/api/v1',
child: MyApp(),
),
);
}
2. Use Auth Middleware
Protect your app with CIAAuthMiddleware:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CIAAuthMiddleware(
config: CIAAuthMiddlewareConfig(
unauthenticatedWidget: CIALoginScreen(
onLoginSuccess: () => Navigator.pushReplacementNamed(context, '/home'),
onRegisterTap: () => Navigator.pushNamed(context, '/register'),
),
verificationWidget: CIAVerificationScreen(
verificationType: context.watchAuth.verificationType,
),
),
child: HomePage(),
),
);
}
}
3. Use Pre-built UI Screens
Login Screen
CIALoginScreen(
branding: CIABrandingConfig(
appName: 'My App',
logoUrl: 'https://example.com/logo.png',
primaryColor: Colors.blue,
showPoweredByCIA: true,
),
enablePhoneLogin: true,
onLoginSuccess: () => Navigator.pushReplacementNamed(context, '/home'),
onRegisterTap: () => Navigator.pushNamed(context, '/register'),
onForgotPasswordTap: () => Navigator.pushNamed(context, '/forgot-password'),
);
Register Screen
CIARegisterScreen(
branding: CIABrandingConfig(appName: 'My App'),
requirePhone: false,
onRegisterSuccess: () {
// Navigate to verification or home
},
onLoginTap: () => Navigator.pop(context),
);
Organization Selector
CIAOrgSelectorScreen(
showExchangeCodeOption: true,
onOrgSelected: (org) {
// Navigate to org-specific content
print('Selected: ${org.name}');
},
onCreateOrgTap: () => Navigator.pushNamed(context, '/create-org'),
onJoinOrgTap: () => Navigator.pushNamed(context, '/join-org'),
);
Manual API Usage
If you prefer to build your own UI:
// Get the auth state
final authState = context.read<CIAAuthState>();
// Login
final success = await authState.login(
email: 'user@example.com',
password: 'password123',
);
// Register
await authState.register(
email: 'user@example.com',
password: 'password123',
firstName: 'John',
lastName: 'Doe',
);
// Phone login
await authState.loginWithPhone(phone: '+976XXXXXXXX');
// Verify OTP
await authState.verifyPhoneOTP(phone: '+976XXXXXXXX', code: '123456');
// Get organizations
await authState.refreshOrganizations();
final orgs = authState.organizations;
// Get organization exchange code (for federated auth)
final exchangeCode = await authState.getOrgExchangeCode('org-id');
// Logout
await authState.logout();
Organization Federated Auth Flow
When your user needs to access an organization-specific server:
// 1. Get exchange code from CIA
final exchangeResponse = await authState.getOrgExchangeCode('org-id');
// 2. Send exchange code to organization's server
// The org server will verify with CIA and issue its own JWT
final orgToken = await yourOrgApi.authenticate(exchangeResponse.exchangeCode);
// 3. Store org token for future use
await authState.apiClient.storage.saveOrgToken(OrgTokenData(
orgId: 'org-id',
accessToken: orgToken.accessToken,
refreshToken: orgToken.refreshToken,
accessTokenExpiry: orgToken.expiresAt,
orgName: exchangeResponse.orgName,
orgServerUrl: 'https://org-server.com',
));
Auth State Listener
Listen to auth state changes:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CIAAuthState>(
builder: (context, authState, _) {
switch (authState.status) {
case CIAAuthStatus.initializing:
return LoadingScreen();
case CIAAuthStatus.authenticated:
return HomeScreen(user: authState.user);
case CIAAuthStatus.unauthenticated:
return LoginScreen();
case CIAAuthStatus.pendingVerification:
return VerificationScreen(type: authState.verificationType);
case CIAAuthStatus.error:
return ErrorScreen(message: authState.errorMessage);
}
},
);
}
}
Secure Storage Details
The SDK uses flutter_secure_storage with optimal security settings:
| Platform | Storage Method | Encryption |
|---|---|---|
| iOS | Keychain | AES-256 |
| Android | EncryptedSharedPreferences | AES-256 GCM |
| macOS | Keychain | AES-256 |
Stored Data
cia_auth_token- Access and refresh tokenscia_org_tokens- Organization-specific tokenscia_user_id- Current user IDcia_pending_verification- Verification state (survives app close)
White-Label Branding
Customize the UI for your app:
CIABrandingConfig(
appName: 'Your App Name',
logoUrl: 'https://your-domain.com/logo.png',
primaryColor: Color(0xFF6366F1), // Indigo
backgroundColor: Color(0xFFF8FAFC),
showPoweredByCIA: true, // Show "Powered by CIA" badge
)
Error Handling
Handle API errors gracefully:
try {
await authState.login(email: email, password: password);
} catch (e) {
if (authState.errorMessage != null) {
// Show error to user
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(authState.errorMessage!)),
);
}
}
// Or check the error details
final apiClient = authState.apiClient;
try {
await apiClient.login(email: email, password: password);
} on CIAApiException catch (e) {
if (e.isAuthError) {
// Invalid credentials
} else if (e.isValidationError) {
// Invalid input
} else if (e.isRateLimited) {
// Too many requests
}
}
Requirements
- Flutter >= 3.0.0
- Dart >= 3.0.0
- iOS 12.0+ (for Keychain)
- Android API 23+ (for EncryptedSharedPreferences)
License
MIT License - See LICENSE file for details.