frica_id_flutter 1.2.0
frica_id_flutter: ^1.2.0 copied to clipboard
Official Flutter SDK for Sign in with Frica ID. OAuth 2.1 PKCE authorization, user management, and seamless authentication for iOS, Android, and Web.
frica_id_flutter #
The official Flutter SDK for Sign in with Frica ID. Implements OAuth 2.1 with PKCE, user authentication, session token refresh, and ready-to-use Flutter UI widgets.
Public Frica ID #
With the profile scope, the user profile exposes an optional publicId, such as FID-7K9MX4QP2N8C. Use it for public account labels. Continue using the opaque sub to match authenticated accounts; its format must not be assumed. Profiles from older servers or without the claim remain supported.
final user = await client.getUserInfo();
print(user.publicId ?? user.name);
The OAuth token endpoint's HTTP 200 response and compact picture URL already work with the SDK. Avatar images are fetched using the returned URL rather than embedded in ID tokens.
Features #
- 🔐 OAuth 2.1 PKCE by Default: Secure authorization flow (RFC 7636). Client secrets are never embedded in mobile code.
- 📱 Cross-Platform: Supports iOS, Android, and Web with deep link redirect callbacks.
- 🎨 Official Branded Widgets: Pre-styled
FricaSignInButtonwith AMOLED pure black (#000000) and Frica Gold (#FFCC00). - 🔄 Token Lifecycle: Built-in token exchange, automatic refresh token rotation, and in-memory or secure storage.
Installation #
Add frica_id_flutter to your pubspec.yaml:
dependencies:
frica_id_flutter: ^1.0.7
Then run:
flutter pub get
Platform Setup #
1. Android Configuration #
Open android/app/src/main/AndroidManifest.xml and add an <intent-filter> inside your <activity android:name=".MainActivity"> to capture the redirect URI callback:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:exported="true">
<!-- Deep linking callback for Frica ID -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Scheme and host should match your Frica ID Developer Console redirect URI -->
<data android:scheme="myapp" android:host="oauth-callback" />
</intent-filter>
</activity>
2. iOS Configuration #
Open ios/Runner/Info.plist and register your custom URL scheme:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Quickstart #
Step 1: Initialize the FricaClient #
import 'package:flutter/material.dart';
import 'package:frica_id_flutter/frica_id_flutter.dart';
final fricaClient = FricaClient(
config: const FricaConfig(
clientId: 'fcli_my_mobile_app', // From https://frica.id/dashboard/developer
redirectUri: 'myapp://oauth-callback',
defaultScope: 'openid profile email phone',
),
);
Step 2: Add the FricaSignInButton #
import 'package:flutter/material.dart';
import 'package:frica_id_flutter/frica_id_flutter.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Dark AMOLED Theme (Default)
FricaSignInButton(
client: fricaClient,
theme: FricaButtonTheme.dark,
size: FricaButtonSize.large,
onError: (err) => print('Login error: $err'),
),
const SizedBox(height: 12),
// Gold Theme
FricaSignInButton(
client: fricaClient,
theme: FricaButtonTheme.gold,
size: FricaButtonSize.medium,
),
],
),
),
);
}
}
Step 3: Handle the Deep Link Callback #
Using standard Flutter app_links or uni_links:
import 'package:app_links/app_links.dart';
void listenToAuthCallbacks(FricaClient client) {
final appLinks = AppLinks();
appLinks.uriLinkStream.listen((uri) async {
if (uri.scheme == 'myapp' && uri.host == 'oauth-callback') {
try {
final tokens = await client.handleRedirectUri(uri);
final user = await client.getUserInfo();
print('Authenticated as ${user.name} (${user.email})');
} catch (e) {
print('Failed to complete login: $e');
}
}
});
}
API Reference #
FricaClient Methods #
| Method | Returns | Description |
|---|---|---|
signIn() |
Future<bool> |
Opens the Frica ID authorization page in system browser |
handleRedirectUri(Uri uri) |
Future<FricaTokenResponse> |
Exchanges authorization code for access and refresh tokens |
getUserInfo() |
Future<FricaUserInfo> |
Retrieves the authenticated user profile |
getValidAccessToken() |
Future<String?> |
Returns a valid access token, auto-refreshing if expired |
refreshToken() |
Future<FricaTokenResponse> |
Refreshes the session using the refresh token |
signOut() |
Future<void> |
Revokes tokens and clears session storage |
isAuthenticated() |
Future<bool> |
Checks if an active session exists |
launchKycVerification(String url) |
Future<bool> |
Launches the hosted KYC verification portal in system browser |
createKycSession(...) |
Future<FricaKycSession> |
Initiates a hosted developer KYC verification session |
getKycVerification(String id) |
Future<FricaKycVerification> |
Retrieves KYC verification result and verified claims |
instantKycCheck(...) |
Future<FricaInstantCheckResult> |
Runs instant identity check against official registries (BVN, NIN) |
getDeveloperWallet(...) |
Future<FricaWallet> |
Queries developer organization token wallet and remaining KYC checks |
topupDeveloperWallet(...) |
Future<Map<String, dynamic>> |
Tops up organization tokens via Card, Mobile Money, Crypto, or Sandbox |
getUserWallet(...) |
Future<FricaWallet> |
Retrieves user sovereign token wallet |
topupUserWallet(...) |
Future<Map<String, dynamic>> |
Tops up user tokens with bonus token support |
payServiceWithTokens(...) |
Future<Map<String, dynamic>> |
Pays for subscription upgrades (Pro, Pro+, Business) with tokens |
verifyDigitalPassport(String id) |
Future<FricaPassportVerificationResult> |
Verifies public authenticity of digital passport QR codes |
KYC & Identity Verification #
Frica ID supports tiered KYC identity verification (TIER_1, TIER_2, TIER_3) across African identity authorities.
Requesting KYC Claims #
Configure defaultScope or request kyc.status and kyc.identity during sign-in:
final fricaClient = FricaClient(
config: FricaConfig(
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'myapp://oauth-callback',
defaultScope: 'openid profile email kyc.status kyc.identity',
),
);
final user = await fricaClient.getUserInfo();
print('KYC Status: ${user.kycStatus}'); // 'APPROVED', 'UNDER_REVIEW', 'PENDING'
print('KYC Tier: ${user.kycTier}'); // 'TIER_2'
print('Is Approved: ${user.isKycApproved}'); // true
print('Legal Name: ${user.kycLegalFirstName} ${user.kycLegalLastName}');
print('Nationality: ${user.kycNationality}');
Launching Hosted Verification #
// Launch hosted KYC verification flow
await fricaClient.launchKycVerification(session.hostedUrl);
Sovereign Wallet & Token Management (FRICA) #
Commercial KYC verifications cost 0.45 FRICA tokens ($0.45 USD). Developers can monitor verification runway and top up balances directly in Flutter apps:
// Check remaining KYC verification runway
final wallet = await fricaClient.getDeveloperWallet(apiKey: 'YOUR_API_KEY');
print('Available Balance: ${wallet.availableBalance} FRICA');
print('Remaining KYC Checks: ${wallet.remainingKycChecks}');
// Top up tokens via Mobile Money (M-Pesa, MTN MoMo)
await fricaClient.topupDeveloperWallet(
amountUsd: 100.0,
paymentMethod: 'MOBILE_MONEY',
mobileMoneyProvider: 'MPESA',
phoneNumber: '+254712345678',
apiKey: 'YOUR_API_KEY',
);
Digital Passport Authenticity Verification #
// Publicly verify scanned QR codes or passport identifiers
final result = await fricaClient.verifyDigitalPassport('FRICA-PASSPORT-KWAME-001');
if (result.verified) {
print('Verified Citizen: ${result.fullName} (${result.country})');
}
License #
Apache-2.0 © Frica ID Team
Release automation #
Bump the package version and update the changelog in a pull request. After a merge to main, passing CI creates an immutable v<version> tag and dispatches the existing publish workflow from that tag. Published versions are skipped; existing tags are never moved. Publishing uses the registry's existing OIDC trusted-publishing configuration, without a long-lived package token.
For pub.dev, enable Publishing from GitHub Actions → workflow_dispatch alongside tag pushes in the package's Admin settings. The dispatched workflow must run on the version tag. Failed publishing runs can be retried from GitHub Actions; do not change an existing release tag.