Azure Active Directory B2C AAD B2C 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.
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.
To secure the token in the app, we can use flutter-secure-storage and navigate to a route’s screen inside the app after successful sign in.
Before we dive in further, let’s talk a little bit more about the scenario we were solving for.
Customer Scenario
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.
Below were the MVP requirements:
- User should be able to login via phone verification (phone number and one time password verification)
- Multi-factor authentication (MFA) should always be enabled
- Email details collection for recovery purpose
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.
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.
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.
Proposed Solution
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.
The best part? Phone verification, email verification and a plethora of other options are readily available to be integrated. This would help us remove other external channels (for OTP, verification, etc.) and use one channel for every activity related to authentication.
A Seamless User Experience
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.
Customer Requirements
Users should be able to:
- Register and login using AAD B2C
- Login using:
- Social providers like Google, Facebook, Microsoft, etc.
- Local accounts like email and password
- Phone number
- One Time Password (OTP)
- Biometrics like fingerprint, face id, etc.
Steps Involved
- Integrate AAD B2C’s login workflow into the mobile application.
- Retrieve the user’s access token and use it to call an API.
- Navigate to a different screen/route after successful login.
There are several ways to achieve this:
- AAD B2C’s default view. This is a web view that redirects to the AAD B2C’s login page in a browser of your choice, and then redirects back to the app after the user has logged in.
- Embedded web view (customer choice and our solution). This option embeds the Azure AD’s webpage in mobile format and provides a native experience.
Azure AD B2C Default View
There are several Flutter packages available for this, one of them being flutter_appauth This package is a wrapper around the AppAuth library, which is a library that allows you to authenticate users using OAuth 2.0 and OpenID Connect. The code for integration, looks like something like this:
import 'package:flutter_appauth/flutter_appauth.dart';
final FlutterAppAuth appAuth = FlutterAppAuth();
final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'<client_id>',
'<redirect_url>',
discoveryUrl: '<discovery_url>',
scopes: ['<scopes>'],
),
);
final idToken = result.idToken;
var accessToken = result.accessToken;
Here, The following outlines details relating to to the code above where:
<client_id>
is the client ID of the app registration in Azure AD B2C.<redirect_url>
is the redirect URL of the app registration in Azure AD B2C.<discovery_url>
is the discovery URL of the app registration in Azure AD B2C.<scopes>
is the scope of the app registration in Azure AD B2C.result.idToken
is the ID token of the user.result.accessToken
is the access token of the user.
An example of the entire code snippet is shown below.
import 'package:flutter/material.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
class LoginScreen extends ConsumerWidget {
@override
build(BuildContext context, ScopedReader watch) {
final appAuth = FlutterAppAuth();
final authState = watch(authStateProvider);
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () async {
try {
final AuthorizationTokenResponse result =
await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'<client_id>',
'<redirect_url>',
discoveryUrl: '<discovery_url>',
scopes: ['<scopes>'],
),
);
final idToken = result.idToken;
var accessToken = result.accessToken;
authState.state = idToken;
} catch (e) {
print(e);
}
},
),
),
);
}
}
When the onPressed method is clicked, it calls the authorizeAndExchangeCode
method of the FlutterAppAuth class
, which redirects the user to the browser, and then back to the app after the user has logged in.
This approach had few drawbacks:
- The user is redirected to the browser, and then back to the app, which is not a great user experience.
- The user’s token is stored in the browser’s cache, which is not secure.
- For a successful redirection to the app, we have to define a appAuthRedirectScheme in build.gradle file. For example: redirect URL becomes
com.example.authapp://oauthredirect
whereoauthredirect
is the callback required by app auth.
Now, let’s see how we achieved the same for the customer, using Azure AD B2C’s embedded web view.
Embedded Web View in Flutter
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.
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.
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.
You can also use the same login screen for both Android and iOS.
Let’s see how we achieved this.
First, we created a new app registration in their AAD B2C tenant. Please refer AAD B2C App Registration for further details. We added a basic redirect URI to their app registration.
Note: The redirect URI should be in the one of the following format:
- Native mobile:
<app_scheme>://<host>/<path>
. For example, if the app scheme is com.example.authapp, the redirect URI will be com.example.authapp://oauthredirect. - Generic redirection:
https:// or http://
. For example, if the redirect URI is https://example.com, the redirect URI will be https://example.com.
Two packages are currently available for achieving embedded web view experience in Flutter.
These are flutter_inappwebview and flutter_webview_plugin.
Code Snippet Using flutter_inappwebview
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse('<login_url>')),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, Uri? url) {
if (url.toString().startsWith('<redirect_url>')) {
Navigator.of(context).pop();
}
},
),
);
}
Code Snippet Using flutter_webview_plugin
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
@override
Widget build(BuildContext context) {
return WebviewScaffold(
url: '<user_flow_run_endpoint>',
appBar: AppBar(
title: Text('Login'),
),
withJavascript: true,
withLocalStorage: true,
withZoom: false,
hidden: true,
initialChild: Container(
color: Colors.white,
child: const Center(
child: Text('Loading...'),
),
),
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
onPageFinished: (String url) {
if (url.toString().startsWith('<redirect_url>')) {
Navigator.pushReplacementNamed(context, '/home');
}
};
},
);
}
Benefits of This Approach
Benefits of the embedded web view over the AAD default view are as follows:
- The user does not have to be redirected to the browser. This is a great user experience.
- The user’s token is stored in the app’s cache (example: one can store in secure storage using flutter-secure-storage package), which is more secure.
- The web view can be customized to match the app’s theme. It can be added with Stack, Positioned, Scaffold and constructed as per app’s requirements.
- One can retrieve the user’s token on successful redirection using
onPageFinished
method and navigate to any route/screen within the app.
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.
This led us to develop an easy-to-use Flutter package aad_b2c_webview which embeds AAD user flow/custom policy within a mobile app.
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.
This has many benefits including:
- Javascript vulnerabilities in the embedded web view is taken care by AAD.
- The PKCE flow returns an authorization code, which is used (in an OAUTH endpoint) to retrieve access and refresh token. Both of which are then securely stored.Nothing is present in browser’s cache.
- No exposure in code, as we use user flow’s endpoint
An example of our current implementation is provided below:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: const Color(0xFF2F56D2),
textTheme: const TextTheme(
headlineLarge: TextStyle(
color: Colors.black,
fontSize: 32,
fontWeight: FontWeight.w700,
fontFamily: 'UberMove',
),
bodyText1: TextStyle(
color: Color(0xFF8A8A8A),
fontSize: 17,
fontWeight: FontWeight.w400,
fontFamily: 'UberMoveText',
),
headline2: TextStyle(
fontSize: 18,
color: Colors.black,
fontWeight: FontWeight.w700,
fontFamily: 'UberMove',
),
),
),
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
// When navigating to the "/" route, build the Create Account widget.
'/': (context) =>
const ADB2CEmbedWebView(
url: '<user_flow_endpoint>',
clientId: '<client_id_of_user_flow>',
redirectUrl: '<redirect_uri_of_user_flow>',
appRedirectRoute: '<route_to_redirect_to_after_sign_in>',
onRedirect: onRedirect,
),
},
);
}
Parameters Used In ADB2CEmbedWebView
- url: This is the user flow/policy endpoint
- clientId: This is client id of the application used for redirection within user flow
- redirectUrl: This is redirect Url of the application used for redirection within user flow
- appRedirectRoute: This is the in-app route to navigate to, after successful sign in on redirect
- onRedirect: This is the callback method to handle the redirect url. This method is called when the redirect url is hit. This method should return the route to navigate to after successful sign in.
Conclusion
- Embedding AAD B2C in a web view provides a seamless user experience for authentication and authorization, while providing protection from security vulnerabilities.
- We can customize all login and registration screens per customer requirements and still have access to all the functionalities and features provided by AAD B2C.
- While both flutter_inappwebview and flutter_webview_plugin readily embeds browser views, they currently lack all the handler and tracking methods for integration with AAD B2C.
- To get started with AAD integration in your mobile application, reference the Flutter package aad_b2c_webview in
pubspec.yaml
. This will embed the web view, securely store the access token and provide a redirection after successful login. - To access the code for the Flutter package, you can also head over to our Github repositoryad_b2c_webview.
We are now trying to achieve the same experience in React Native, and hope we can share our learnings here soon.
References
Azure AD B2C Flutter aad_b2c_web view flutter package AAD B2C Webview Repository