import 'dart:io';
import 'package:common/storage/common_preferences.dart';
import 'package:common/utils/env_utils.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:msal_auth/msal_auth.dart';
import '../gen/assets.gen.dart';
const _msalChannel = MethodChannel('msal_auth');
class MSALService {
final scopes = dotenv.ENTRA_SCOPES.split(',');
final authorityUrl = dotenv.ENTRA_ID_AUTHORITY_URL;
final clientId = dotenv.ENTRA_ID_CLIENT_ID;
final redirect = dotenv.ENTRA_ID_REDIRECT_URL;
late final SingleAccountPca msAuth;
final _preferences = CommonPreferences();
MSALService();
Future<void> init() async {
try {
await _preferences.init();
final configFilePath = switch (appFlavor?.toLowerCase()) {
"develop" => "packages/common/${Assets.msal.msalConfigDev}",
"stage" => "packages/common/${Assets.msal.msalConfigStage}",
"prod" => "packages/common/${Assets.msal.msalConfigProd}",
_ => throw Exception("Missing config file for flavor $appFlavor"),
};
msAuth = await SingleAccountPca.create(
clientId: clientId,
androidConfig: AndroidConfig(configFilePath: configFilePath, redirectUri: redirect),
appleConfig: AppleConfig(
authorityType: AuthorityType.aad,
broker: Broker.webView,
authority: authorityUrl,
),
);
// if (Platform.isIOS) await _clearStaleAccounts();
} catch (e) {
print("qqq msauth init=$e");
}
}
/// Removes all cached accounts from the MSAL keychain cache.
/// This prevents the "Multiple accounts found in cache" error
/// that breaks SingleAccountPca on iOS.
Future<void> _clearStaleAccounts() async {
try {
final result = await _msalChannel.invokeMethod('getAccounts');
if (result is List && result.length > 1) {
for (final account in result) {
final id = (account as Map)['id'] as String?;
if (id != null) {
try {
await _msalChannel.invokeMethod('removeAccount', id);
} catch (_) {}
}
}
}
} catch (e) {
print('qqq _clearStaleAccounts: $e');
}
}
Future<void> openAuth(Prompt prompt) async {
try {
await msAuth.acquireToken(
scopes: scopes,
prompt: prompt,
customWebViewConfig: const CustomWebViewConfig(),
authority: authorityUrl,
);
await _preferences.setIsLoggedIn(true);
} catch (e) {
await logout();
rethrow;
}
}
Future<String> acquireTokenSilent() async {
try {
final response = await msAuth.acquireTokenSilent(scopes: scopes, authority: authorityUrl);
await _preferences.setIsLoggedIn(true);
return response.accessToken;
} catch (e) {
await logout();
rethrow;
}
}
Future<void> logout() async {
await _preferences.setIsLoggedIn(false);
try {
await _clearStaleAccounts();
await msAuth.signOut();
} catch (e) {
// Ignore sign-out errors
}
}
bool get isLoggedIn => _preferences.isLoggedIn;
}