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 tokens
  • cia_org_tokens - Organization-specific tokens
  • cia_user_id - Current user ID
  • cia_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.

Libraries

flutter_cia