6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit f9e49408

AuthorMikkel Thygesen<mth@dwarf.dk>
Date2025-08-22 13:18:32 +0200
2094: Replaced MSAL with Entra ID

Changed files

.../android/app/src/main/AndroidManifest.xml       | 102 ++++++++++++---------
 .../authentication/authentication_repository.dart  |   5 +
 comwell_key_app/lib/b2c_auth/b2c_auth_page.dart    |  23 -----
 .../lib/b2c_auth/bloc/b2c_auth_cubit.dart          |  77 ----------------
 comwell_key_app/lib/login/cubit/login_cubit.dart   |  41 ++++++++-
 comwell_key_app/lib/login/login_page.dart          |   9 +-
 .../profile_settings/profile_settings_page.dart    |   6 +-
 comwell_key_app/lib/routing/app_router.dart        |  55 ++++-------
 comwell_key_app/lib/routing/app_routes.dart        |   3 -
 comwell_key_app/pubspec.yaml                       |   1 +
 10 files changed, 129 insertions(+), 193 deletions(-)

Diff

diff --git a/comwell_key_app/android/app/src/main/AndroidManifest.xml b/comwell_key_app/android/app/src/main/AndroidManifest.xml
index f8a2d868..75d1f195 100644
--- a/comwell_key_app/android/app/src/main/AndroidManifest.xml
+++ b/comwell_key_app/android/app/src/main/AndroidManifest.xml
@@ -2,39 +2,39 @@
<uses-feature
android:name="android.hardware.nfc.hce"
- android:required="false"/>
+ android:required="false" />
- <queries>
- <!-- If your app checks for SMS support -->
- <intent>
- <action android:name="android.intent.action.VIEW" />
- <data android:scheme="sms" />
- </intent>
- <!-- If your app checks for call support -->
- <intent>
- <action android:name="android.intent.action.VIEW" />
- <data android:scheme="tel" />
- </intent>
- <!-- If your application checks for inAppBrowserView launch mode support -->
- <intent>
- <action android:name="android.support.customtabs.action.CustomTabsService" />
- </intent>
- <intent>
- <action android:name="android.intent.action.SENDTO" />
- <data android:scheme="mailto" />
- </intent>
- </queries>
+ <queries>
+ <!-- If your app checks for SMS support -->
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="sms" />
+ </intent>
+ <!-- If your app checks for call support -->
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="tel" />
+ </intent>
+ <!-- If your application checks for inAppBrowserView launch mode support -->
+ <intent>
+ <action android:name="android.support.customtabs.action.CustomTabsService" />
+ </intent>
+ <intent>
+ <action android:name="android.intent.action.SENDTO" />
+ <data android:scheme="mailto" />
+ </intent>
+ </queries>
<application
- android:label="@string/app_name"
android:name="${applicationName}"
- android:icon="@mipmap/launcher_icon">
+ android:icon="@mipmap/launcher_icon"
+ android:label="@string/app_name">
<activity
android:name=".MainActivity"
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
+ android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
- android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
- android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@@ -45,39 +45,57 @@
android:resource="@style/AppTheme" />
<intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- <meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
- <intent-filter android:autoVerify="true">
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <!-- <data android:scheme="https" android:host="comwellservicesdev.b2clogin.com" android:path="/oauthredirect" /> -->
- <!-- <data android:scheme="msal40304e05-f354-49b2-9934-1b42f311751a" android:host="/auth" /> -->
+ <meta-data
+ android:name="flutter_deeplinking_enabled"
+ android:value="true" />
+
+ <intent-filter>
<data
android:host="${applicationId}"
android:path="/adyenPayment"
android:scheme="adyencheckout" />
-
<data
android:host="comwell.app"
- android:pathPrefix="/booking"
- android:pathPattern=".*"
android:scheme="app" />
</intent-filter>
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <!-- <data android:scheme="https" android:host="comwellservicesdev.b2clogin.com" android:path="/oauthredirect" /> -->
+ <!-- <data android:scheme="msal40304e05-f354-49b2-9934-1b42f311751a" android:host="/auth" /> -->
+
+ <data
+ android:host="comwell-v2.ddev.site"
+ android:path="/api/auth/login/callback"
+ android:scheme="https" />
+
+ </intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
- <meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
- <meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="false" />
- <meta-data android:name="google_analytics_default_allow_ad_storage" android:value="false" />
- <meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="false" />
- <meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="false" />
+ <meta-data
+ android:name="firebase_analytics_collection_enabled"
+ android:value="false" />
+ <meta-data
+ android:name="google_analytics_default_allow_analytics_storage"
+ android:value="false" />
+ <meta-data
+ android:name="google_analytics_default_allow_ad_storage"
+ android:value="false" />
+ <meta-data
+ android:name="google_analytics_default_allow_ad_user_data"
+ android:value="false" />
+ <meta-data
+ android:name="google_analytics_default_allow_ad_personalization_signals"
+ android:value="false" />
</application>
</manifest>
diff --git a/comwell_key_app/lib/authentication/authentication_repository.dart b/comwell_key_app/lib/authentication/authentication_repository.dart
index 9bf0f5d9..07dcec15 100644
--- a/comwell_key_app/lib/authentication/authentication_repository.dart
+++ b/comwell_key_app/lib/authentication/authentication_repository.dart
@@ -87,4 +87,9 @@ class AuthenticationRepository {
final accessToken = await secureStorage.read(constants.accessToken);
return refreshToken != null && accessToken != null;
}
+
+ Future<void> loginWithCode(String code) async {
+ await secureStorage.write(constants.accessToken, code);
+ await logIn();
+ }
}
diff --git a/comwell_key_app/lib/b2c_auth/b2c_auth_page.dart b/comwell_key_app/lib/b2c_auth/b2c_auth_page.dart
deleted file mode 100644
index 6974f6f5..00000000
--- a/comwell_key_app/lib/b2c_auth/b2c_auth_page.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-import 'package:aad_b2c_webview/aad_b2c_webview.dart';
-import 'package:comwell_key_app/b2c_auth/bloc/b2c_auth_cubit.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class B2cAuthPage extends StatelessWidget {
- const B2cAuthPage({super.key});
-
- @override
- Widget build(BuildContext context) {
- return BlocBuilder<B2CAuthCubit, B2CAuthState>(builder: (context, state) {
- final cubit = context.read<B2CAuthCubit>();
- return SafeArea(
- child: Scaffold(
- body: AADB2CBase.webview(
- params: cubit.getB2CParams(),
- settings: cubit.getWebSettings(),
- ),
- ),
- );
- });
- }
-}
diff --git a/comwell_key_app/lib/b2c_auth/bloc/b2c_auth_cubit.dart b/comwell_key_app/lib/b2c_auth/bloc/b2c_auth_cubit.dart
deleted file mode 100644
index 4cd7ff5b..00000000
--- a/comwell_key_app/lib/b2c_auth/bloc/b2c_auth_cubit.dart
+++ /dev/null
@@ -1,77 +0,0 @@
-import 'package:aad_b2c_webview/aad_b2c_webview.dart';
-import 'package:bloc/bloc.dart';
-import 'package:comwell_key_app/authentication/authentication_repository.dart';
-import 'package:comwell_key_app/login/auth.dart';
-import 'package:comwell_key_app/tracking/comwell_tracking.dart';
-import 'package:comwell_key_app/utils/secure_storage.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_dotenv/flutter_dotenv.dart';
-import 'package:go_router/go_router.dart';
-import 'package:jwt_decoder/jwt_decoder.dart';
-import 'package:comwell_key_app/common/const.dart' as constants;
-
-class B2CAuthCubit extends Cubit<B2CAuthState> {
- B2CAuthCubit({
- required this.authType,
- required this.tracking,
- required this.authenticationRepository,
- }) : super(B2CAuthState());
-
- final Auth authType;
- final ComwellTracking tracking;
- final SecureStorage secureStorage = SecureStorage();
- final AuthenticationRepository authenticationRepository;
-
- void onIdToken(String token) async {
- debugPrint(JwtDecoder.decode(token).toString());
- tracking.trackLogin();
- secureStorage.write(constants.accessToken, token);
- }
-
- void _onRefreshToken(String token) {
- debugPrint('refresh_token: $token');
- secureStorage.write(constants.refreshToken, token);
- authenticationRepository.logIn();
- }
-
- void _onB2CSuccess(BuildContext context, _, TokenEntity? idToken,
- TokenEntity? refreshToken) {
- final idTokenValue = idToken?.value;
- final refreshTokenValue = refreshToken?.value;
- if (idTokenValue != null) onIdToken(idTokenValue);
- if (refreshTokenValue != null) _onRefreshToken(refreshTokenValue);
- if (context.mounted) context.pop();
- }
-
- void _onB2CError(BuildContext context, String? error) {
- authenticationRepository.logOut(forced: true);
- print("qqq error=$error");
- if (context.mounted) context.pop();
- }
-
- B2CWebViewParams getB2CParams() {
- return B2CWebViewParams(
- tenantBaseUrl: dotenv.env['AAD_B2C_USER_AUTH_FLOW']!,
- clientId: dotenv.env['AAD_B2C_CLIENT_ID']!,
- redirectUrl: dotenv.env['AAD_B2C_REDIRECT_URL']!,
- scopes: dotenv.env['AAD_B2C_SCOPES']!.split(','),
- userFlowName: switch (authType) {
- Auth.login => dotenv.env['AAD_B2C_USER_FLOW_NAME_LOGIN']!,
- Auth.createUser => dotenv.env['AAD_B2C_USER_FLOW_NAME_CREATE_USER']!,
- Auth.changePassword =>
- dotenv.env['AAD_B2C_USER_FLOW_NAME_CHANGE_PASSWORD']!,
- },
- isLoginFlow: true,
- containsChallenge: true,
- );
- }
-
- WebViewSettingsEntity getWebSettings() {
- return WebViewSettingsEntity(
- onError: _onB2CError,
- onSuccess: _onB2CSuccess,
- controllerBuilder: (context, controller) {});
- }
-}
-
-class B2CAuthState {}
diff --git a/comwell_key_app/lib/login/cubit/login_cubit.dart b/comwell_key_app/lib/login/cubit/login_cubit.dart
index 3321748e..2b4fb8ec 100644
--- a/comwell_key_app/lib/login/cubit/login_cubit.dart
+++ b/comwell_key_app/lib/login/cubit/login_cubit.dart
@@ -1,11 +1,48 @@
import 'package:bloc/bloc.dart';
+import 'package:comwell_key_app/login/auth.dart';
import 'package:equatable/equatable.dart';
-
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:uri/uri.dart';
part 'login_state.dart';
class LoginCubit extends Cubit<LoginState> {
LoginCubit({required this.forced}) : super(LoginState());
-
final bool forced;
+ final x = """
+ https://comwelliddev.ciamlogin.com
+ /8392ed03-7f96-414a-bc94-4f16dc0b9cf4/oauth2/v2.0/authorize?
+ client_id=8310f76d-4cc5-4270-bc8c-1fea1600525d&
+ redirect_uri=https%3A%2F%2Fcomwell-v2.ddev.site%2Fapi%2Fauth%2Flogin%2Fcallback&
+ scope=email+User.Read+profile+openid+offline_access&
+ response_type=code&
+ state=pk2a9qBAiCVULntawNHCiEzRA3IWyREtrTH5GE0k&
+ sso_reload=true
+ """;
+
+ Uri constructUrl(Auth authType) {
+ final tenantName = dotenv.env["ENTRA_ID_TENANT_NAME"]!;
+ final tenantId = dotenv.env["ENTRA_ID_TENANT_ID"]!;
+ final clientId = dotenv.env["ENTRA_ID_CLIENT_ID"]!;
+ const redirect = "https://comwell-v2.ddev.site/api/auth/login/callback";
+ const scopes = "openid"; //"email+User.Read+profile+openid+offline_access";
+ const responseType = "code";
+ final state = dotenv.env["ENTRA_ID_STATE"]!;
+ const ssoReload = true;
+ final uri = Uri.parse(
+ "https://$tenantName.ciamlogin.com/$tenantId/oauth2/v2.0/authorize",
+ );
+ final queryParams = {
+ "redirect_uri": redirect,
+ "client_id": clientId,
+ "scope": scopes,
+ "response_type": responseType,
+ "state": state,
+ "sso_reload": "$ssoReload"
+ };
+ UriBuilder uriBuilder = UriBuilder.fromUri(uri);
+ uriBuilder.queryParameters.addAll(queryParams);
+ print("qqq uri=${uriBuilder.build()}");
+ return uriBuilder.build();
+ }
}
diff --git a/comwell_key_app/lib/login/login_page.dart b/comwell_key_app/lib/login/login_page.dart
index 2050ef8f..9482d33e 100644
--- a/comwell_key_app/lib/login/login_page.dart
+++ b/comwell_key_app/lib/login/login_page.dart
@@ -1,10 +1,10 @@
+import 'package:comwell_key_app/login/auth.dart';
import 'package:comwell_key_app/login/components/create_user_button.dart';
import 'package:comwell_key_app/login/components/forced_logout_banner.dart';
import 'package:comwell_key_app/login/components/login_button.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
+import 'package:url_launcher/url_launcher.dart';
import 'cubit/login_cubit.dart';
@@ -42,13 +42,14 @@ class LoginPage extends StatelessWidget {
// Login Button
LoginButton(
onPressed: () {
- context.push("/${AppRoutes.b2cLogin.name}");
+ launchUrl(cubit.constructUrl(Auth.login));
},
),
const SizedBox(height: 10),
// Create New User Button
CreateUserButton(onPressed: () {
- context.push("/${AppRoutes.b2cSignUp.name}");
+ //context.push("/${AppRoutes.b2cSignUp.name}");
+ launchUrl(cubit.constructUrl(Auth.createUser));
}),
const SizedBox(height: 20),
],
diff --git a/comwell_key_app/lib/profile_settings/profile_settings_page.dart b/comwell_key_app/lib/profile_settings/profile_settings_page.dart
index 357ff107..74aa6aab 100644
--- a/comwell_key_app/lib/profile_settings/profile_settings_page.dart
+++ b/comwell_key_app/lib/profile_settings/profile_settings_page.dart
@@ -7,14 +7,12 @@ import 'package:comwell_key_app/profile_settings/components/intl_phone_field.dar
import 'package:comwell_key_app/profile_settings/components/text_field_with_trailing_icon.dart';
import 'package:comwell_key_app/profile_settings/cubit/profile_settings_cubit.dart';
import 'package:comwell_key_app/profile_settings/model/address.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
import 'package:comwell_key_app/themes/light_theme.dart';
import 'package:comwell_key_app/utils/address_utils.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
-import 'package:go_router/go_router.dart';
import '../common/components/shimmer_loader/profile_settings_shimmer_loader.dart';
class ProfileSettingsPage extends StatelessWidget {
@@ -141,7 +139,9 @@ class ProfileSettingsPage extends StatelessWidget {
text: "profile_settings_edit_password".tr(),
trailingIcon: "assets/icons/arrow-left.svg",
onTap: () {
- context.push("/${AppRoutes.b2cForgotPassword.name}");
+ /**
+ * We dont know how to handle change password with Entra ID yet
+ */
},
showTitle: false),
const SizedBox(height: 20),
diff --git a/comwell_key_app/lib/routing/app_router.dart b/comwell_key_app/lib/routing/app_router.dart
index 04ee36e5..0a1ffd60 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -1,7 +1,5 @@
import 'package:comwell_key_app/authentication/authentication_repository.dart';
import 'package:comwell_key_app/authentication/enum/authentication_status.dart';
-import 'package:comwell_key_app/b2c_auth/b2c_auth_page.dart';
-import 'package:comwell_key_app/b2c_auth/bloc/b2c_auth_cubit.dart';
import 'package:comwell_key_app/check_in/bloc/check_in_cubit.dart';
import 'package:comwell_key_app/check_in/check_in_page.dart';
import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
@@ -26,7 +24,6 @@ import 'package:comwell_key_app/hotel_information/cubit/hotel_information_cubit.
import 'package:comwell_key_app/hotel_information/hotel_information_page.dart';
import 'package:comwell_key_app/housekeeping/housekeeping_page.dart';
import 'package:comwell_key_app/key/key_page.dart';
-import 'package:comwell_key_app/login/auth.dart';
import 'package:comwell_key_app/login/cubit/login_cubit.dart';
import 'package:comwell_key_app/login/login_page.dart';
import 'package:comwell_key_app/my_booking/cubit/my_booking_cubit.dart';
@@ -214,9 +211,23 @@ GoRouter goRouter() {
);
}),
GoRoute(
- path: "/oauthredirect",
+ path: "/api/auth/login/callback",
name: AppRoutes.overview.name,
- builder: (context, state) => const OverviewPage(),
+ builder: (context, state) {
+ print("qqq keys=${state.uri.queryParameters.keys.toList()}");
+ final code = state.uri.queryParameters['code'] ?? '';
+ final uriState = state.uri.queryParameters['state'] ?? '';
+ final sessionState =
+ state.uri.queryParameters['session_state'] ?? '';
+ print("${{
+ "qqq": "",
+ "code": code,
+ "uriState": uriState,
+ "sessionState": sessionState
+ }}");
+ authRepo.loginWithCode(code);
+ return const OverviewPage();
+ },
routes: [
GoRoute(
path: "profile",
@@ -331,40 +342,6 @@ GoRouter goRouter() {
name: AppRoutes.redeem.name,
builder: (context, state) => const RedeemPage(),
),
- GoRoute(
- path: "/${AppRoutes.b2cLogin.name}",
- builder: (context, state) {
- return BlocProvider(
- create: (context) => B2CAuthCubit(
- authType: Auth.login,
- tracking: locator.get(),
- authenticationRepository: locator.get(),
- ),
- child: const B2cAuthPage());
- }),
- GoRoute(
- path: "/${AppRoutes.b2cSignUp.name}",
- builder: (context, state) {
- return BlocProvider(
- create: (context) => B2CAuthCubit(
- authType: Auth.createUser,
- tracking: locator.get(),
- authenticationRepository: locator.get(),
- ),
- child: const B2cAuthPage(),
- );
- }),
- GoRoute(
- path: "/${AppRoutes.b2cForgotPassword.name}",
- builder: (context, state) {
- return BlocProvider(
- create: (context) => B2CAuthCubit(
- authType: Auth.changePassword,
- tracking: locator.get(),
- authenticationRepository: locator.get(),
- ),
- child: const B2cAuthPage());
- }),
GoRoute(
path: "/${AppRoutes.preregistration.name}",
name: AppRoutes.preregistration.name,
diff --git a/comwell_key_app/lib/routing/app_routes.dart b/comwell_key_app/lib/routing/app_routes.dart
index 1b0ab301..bd6c8b5b 100644
--- a/comwell_key_app/lib/routing/app_routes.dart
+++ b/comwell_key_app/lib/routing/app_routes.dart
@@ -37,7 +37,4 @@ enum AppRoutes {
shareRoom,
forceUpdate,
upSalesError,
- b2cLogin,
- b2cSignUp,
- b2cForgotPassword,
}
diff --git a/comwell_key_app/pubspec.yaml b/comwell_key_app/pubspec.yaml
index d32bf304..d881b2b7 100644
--- a/comwell_key_app/pubspec.yaml
+++ b/comwell_key_app/pubspec.yaml
@@ -58,6 +58,7 @@ dependencies:
snapping_sheet: ^3.1.0
shimmer: ^3.0.0
in_app_update: ^4.2.3
+ uri: ^1.0.0
dependency_overrides:
#Remove override when slider button updates