{"id":14586,"date":"2023-03-20T12:00:00","date_gmt":"2023-03-20T19:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cse\/?p=14586"},"modified":"2024-07-18T11:51:57","modified_gmt":"2024-07-18T18:51:57","slug":"azure_ad_b2c_flutter","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/azure_ad_b2c_flutter\/","title":{"rendered":"In-App User Experience with Flutter Embedded Web View and Azure Active Directory B2C User Flows"},"content":{"rendered":"
Azure Active Directory B2C AAD B2C<\/a> is a cloud-based Identity and Access Management service that enables you to customize and control the user sign-up, sign-in, and profile management process.<\/p>\n This article will walk you through several ways on how we can integrate AAD B2C’s user login workflow within mobile app development using Flutter.<\/p>\n To secure the token in the app, we can use flutter-secure-storage<\/a> and navigate to a route’s screen inside the app after successful sign in.<\/p>\n Before we dive in further, let’s talk a little bit more about the scenario we were solving for.<\/p>\n One of our customers was actively building a state-of-the-art mobile application using the open-source framework Flutter. Flutter currently stands at #2, after React Native, for mobile development. The customer was facing a major challenge on how to tackle authentication & authorization within the app.<\/p>\n Below were the MVP requirements:<\/p>\n Previously, they were using a basic way of storing username and password using Postgres database and a user model. To enable the one-time password feature, they were using an external SMS provider which created additional cost and overhead.<\/p>\n This approach, though simple, was highly insecure and highly probable to anomalous attacks and breaches. As the mobile application was going to serve a very large geographical customer base, having a standard PKCE (Proof Key for Code Exchange) flow was the need of the hour.<\/p>\n PKCE is an OpenId Connect flow specifically designed to authenticate native or mobile application users. It is an extension to the Authorization Code flow to prevent CSRF (Cross site request forgery) and authorization code injection attacks.<\/p>\n AAD B2C integration and implementation for PKCE flow. AAD provides out-of-the-box solutions to enable PKCE authentication via user flows or custom policies.<\/p>\n The best part?<\/strong>\nPhone verification, email verification and a plethora of other options are readily available to be integrated.\nThis would help us remove other external channels (for OTP, verification, etc.) and use one channel for every activity related to authentication.<\/p>\n As a part of implementation, we wanted to provide a seamless experience to the customer’s users by not redirecting them to a browser for authentication. We also wanted to provide a native experience to their users for registration and authentication.<\/p>\n Users should be able to:<\/p>\n There are several ways to achieve this:<\/p>\n There are several Flutter packages available for this, one of them being flutter_appauth<\/a>\nThis package is a wrapper around the AppAuth<\/a> library, which is a library that allows you to authenticate users using OAuth 2.0 and OpenID Connect.\nThe code for integration, looks like something like this:<\/p>\n Here,\nThe following outlines details relating to to the code above where:<\/p>\n An example of the entire code snippet is shown below.<\/p>\n When the onPressed method is clicked, it calls the This approach had few drawbacks:<\/p>\n Now, let’s see how we achieved the same for the customer, using Azure AD B2C’s embedded web view.<\/p>\n Because embedded web view is native to the app, it provides a better user experience by not redirecting the user to the browser. As an added bonus, it is more secure than the default view.<\/p>\n Just imagine, once you have UI customization on top of AAD B2C and you use this approach, you can have a seamless experience for your users.<\/p>\n Everything from Multi-Factor Authentication (MFA) to OTP verification will happen within the app. This gives the user a great experience and, at the same time, everything is happening securely within AAD.<\/p>\n You can also use the same login screen for both Android and iOS.<\/p>\n Let’s see how we achieved this.<\/p>\n First, we created a new app registration in their AAD B2C tenant. Please refer AAD B2C App Registration<\/a> for further details.\nWe added a basic redirect URI to their app registration.<\/p>\n Note: The redirect URI should be in the one of the following format:<\/p>\n Two packages are currently available for achieving embedded web view experience in Flutter.<\/p>\n These are flutter_inappwebview<\/a> and flutter_webview_plugin<\/a>.<\/p>\n Benefits of the embedded web view over the AAD default view are as follows:<\/p>\n While working with the customer and integrating embedded web view of a user flow for phone verification, we noticed that our code was actually reusable and can be ideally imported as a Flutter package.<\/p>\n This led us to develop an easy-to-use Flutter package aad_b2c_webview<\/a> which embeds AAD user flow\/custom policy within a mobile app.<\/p>\n The entire experience is one where a user is registering and logging in within the app, but everything is actually happening in an embedded browser of AAD.<\/p>\n This has many benefits including:<\/p>\n An example of our current implementation is provided below:<\/p>\n We are now trying to achieve the same experience in React Native, and hope we can share our learnings here soon.<\/p>\n Azure AD B2C<\/a>\nFlutter<\/a>\naad_b2c_web view flutter package<\/a>\nAAD B2C Webview Repository<\/a><\/p>\n","protected":false},"excerpt":{"rendered":" Guidance on how we can integrate Azure AD B2C’s user login workflow within mobile app development using Flutter.<\/p>\n","protected":false},"author":114914,"featured_media":14587,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[60,3388,3366,3364],"class_list":["post-14586","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","tag-azure","tag-flutter","tag-frameworks","tag-open-source"],"acf":[],"blog_post_summary":" Guidance on how we can integrate Azure AD B2C’s user login workflow within mobile app development using Flutter.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/14586","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/114914"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=14586"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/14586\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/14587"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=14586"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=14586"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=14586"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}Customer Scenario<\/h2>\n
\n
Proposed Solution<\/h2>\n
A Seamless User Experience<\/h2>\n
Customer Requirements<\/h3>\n
\n
\n
Steps Involved<\/h2>\n
\n
\n
Azure AD B2C Default View<\/h2>\n
import 'package:flutter_appauth\/flutter_appauth.dart';\r\nfinal FlutterAppAuth appAuth = FlutterAppAuth();\r\nfinal AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(\r\n AuthorizationTokenRequest(\r\n '<client_id>',\r\n '<redirect_url>',\r\n discoveryUrl: '<discovery_url>',\r\n scopes: ['<scopes>'],\r\n ),\r\n);\r\nfinal idToken = result.idToken;\r\nvar accessToken = result.accessToken;<\/code><\/pre>\n
\n
<client_id><\/code> is the client ID of the app registration in Azure AD B2C.<\/li>\n
<redirect_url><\/code> is the redirect URL of the app registration in Azure AD B2C.<\/li>\n
<discovery_url><\/code> is the discovery URL of the app registration in Azure AD B2C.<\/li>\n
<scopes><\/code> is the scope of the app registration in Azure AD B2C.<\/li>\n
result.idToken<\/code> is the ID token of the user.<\/li>\n
result.accessToken<\/code> is the access token of the user.<\/li>\n<\/ul>\n
import 'package:flutter\/material.dart';\r\nimport 'package:flutter_appauth\/flutter_appauth.dart';\r\nclass LoginScreen extends ConsumerWidget {\r\n\r\n @override\r\n build(BuildContext context, ScopedReader watch) {\r\n final appAuth = FlutterAppAuth();\r\n final authState = watch(authStateProvider);\r\n return Scaffold(\r\n appBar: AppBar(\r\n title: Text('Login'),\r\n ),\r\n body: Center(\r\n child: ElevatedButton(\r\n child: Text('Login'),\r\n onPressed: () async {\r\n try {\r\n final AuthorizationTokenResponse result =\r\n await appAuth.authorizeAndExchangeCode(\r\n AuthorizationTokenRequest(\r\n '<client_id>',\r\n '<redirect_url>',\r\n discoveryUrl: '<discovery_url>',\r\n scopes: ['<scopes>'],\r\n ),\r\n );\r\n final idToken = result.idToken;\r\n var accessToken = result.accessToken;\r\n authState.state = idToken;\r\n } catch (e) {\r\n print(e);\r\n }\r\n },\r\n ),\r\n ),\r\n );\r\n } \r\n}<\/code><\/pre>\n
authorizeAndExchangeCode<\/code> method of the
FlutterAppAuth class<\/code>, which redirects the user to the browser, and then back to the app after the user has logged in.<\/p>\n
\n
com.example.authapp:\/\/oauthredirect<\/code> where
oauthredirect<\/code> is the callback required by app auth.<\/li>\n<\/ul>\n
Embedded Web View in Flutter<\/h2>\n
\n
<app_scheme>:\/\/<host>\/<path><\/code>.\nFor example, if the app scheme is com.example.authapp, the redirect URI will be com.example.authapp:\/\/oauthredirect.<\/li>\n
https:\/\/ or http:\/\/<\/code>.\nFor example, if the redirect URI is https:\/\/example.com, the redirect URI will be https:\/\/example.com.<\/li>\n<\/ul>\n
Code Snippet Using flutter_inappwebview<\/h3>\n
import 'package:flutter\/material.dart';\r\nimport 'package:flutter_inappwebview\/flutter_inappwebview.dart';\r\n@override\r\nWidget build(BuildContext context) {\r\n return Scaffold(\r\n appBar: AppBar(\r\n title: Text('Login'),\r\n ),\r\n body: InAppWebView(\r\n initialUrlRequest: URLRequest(url: Uri.parse('<login_url>')),\r\n initialOptions: InAppWebViewGroupOptions(\r\n crossPlatform: InAppWebViewOptions(\r\n useShouldOverrideUrlLoading: true,\r\n ),\r\n ),\r\n onWebViewCreated: (InAppWebViewController controller) {\r\n webView = controller;\r\n },\r\n onLoadStart: (InAppWebViewController controller, Uri? url) {\r\n if (url.toString().startsWith('<redirect_url>')) {\r\n Navigator.of(context).pop();\r\n }\r\n },\r\n ),\r\n );\r\n}<\/code><\/pre>\n
Code Snippet Using flutter_webview_plugin<\/h3>\n
import 'package:flutter\/material.dart';\r\nimport 'package:flutter_webview_plugin\/flutter_webview_plugin.dart';\r\n@override\r\nWidget build(BuildContext context) {\r\n return WebviewScaffold(\r\n url: '<user_flow_run_endpoint>',\r\n appBar: AppBar(\r\n title: Text('Login'),\r\n ),\r\n withJavascript: true,\r\n withLocalStorage: true,\r\n withZoom: false,\r\n hidden: true,\r\n initialChild: Container(\r\n color: Colors.white,\r\n child: const Center(\r\n child: Text('Loading...'),\r\n ),\r\n ),\r\n onWebViewCreated: (WebViewController webViewController) {\r\n _controller.complete(webViewController);\r\n onPageFinished: (String url) {\r\n if (url.toString().startsWith('<redirect_url>')) {\r\n Navigator.pushReplacementNamed(context, '\/home');\r\n }\r\n };\r\n },\r\n );\r\n}<\/code><\/pre>\n
Benefits of This Approach<\/h3>\n
\n
onPageFinished<\/code> method and navigate to any route\/screen within the app.<\/li>\n<\/ul>\n
\n
@override\r\nWidget build(BuildContext context) {\r\n return MaterialApp(\r\n title: 'Flutter Demo',\r\n theme: ThemeData(\r\n primaryColor: const Color(0xFF2F56D2),\r\n textTheme: const TextTheme(\r\n headlineLarge: TextStyle(\r\n color: Colors.black,\r\n fontSize: 32,\r\n fontWeight: FontWeight.w700,\r\n fontFamily: 'UberMove',\r\n ),\r\n bodyText1: TextStyle(\r\n color: Color(0xFF8A8A8A),\r\n fontSize: 17,\r\n fontWeight: FontWeight.w400,\r\n fontFamily: 'UberMoveText',\r\n ),\r\n headline2: TextStyle(\r\n fontSize: 18,\r\n color: Colors.black,\r\n fontWeight: FontWeight.w700,\r\n fontFamily: 'UberMove',\r\n ),\r\n ),\r\n ),\r\n debugShowCheckedModeBanner: false,\r\n initialRoute: '\/',\r\n routes: {\r\n \/\/ When navigating to the \"\/\" route, build the Create Account widget.\r\n '\/': (context) =>\r\n const ADB2CEmbedWebView(\r\n url: '<user_flow_endpoint>',\r\n clientId: '<client_id_of_user_flow>',\r\n redirectUrl: '<redirect_uri_of_user_flow>',\r\n appRedirectRoute: '<route_to_redirect_to_after_sign_in>',\r\n onRedirect: onRedirect,\r\n ),\r\n },\r\n );\r\n}<\/code><\/pre>\n
Parameters Used In ADB2CEmbedWebView<\/h2>\n
\n
Conclusion<\/h2>\n
\n
pubspec.yaml<\/code>. This will embed the web view, securely store the access token and provide a redirection after successful login.<\/li>\n
References<\/h2>\n