Search code examples
flutterauthenticationazure-ad-b2cazure-ad-msalmsal.js

Flutter MSAL_js-based (B2C) Authentication fails


EDIT: The real culprit was the https: in https:localhost:8080. My browser couldn't handle this properly. Switching to http:// resolved the issue! How I need to figure out how to make my browser work properly with the https-enabled localhost.


OP: I've been trying to set up the Authentication flow with AD B2C in Flutter.

I have configured the tenant, the app, the userflow called B2C_1_SignUpSignIn with the E-Mail/Password and additionally the Google identity providers. Both have been tested, both work in portal.

Now, I have found this example online of how to use MSAL_js (the flutter wrapper for msal.js). https://github.com/Francessco121/msal-js-dart/tree/master/example/flutter_example

I've added (as per the example)

  <!-- MSAL.js -->
  <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js"
    integrity="sha384-ggh+EF1aSqm+Y4yvv2n17KpurNcZTeYtUZUvhPziElsstmIEubyEB6AIVpKLuZgr" crossorigin="anonymous">
    </script>

to my web/index.html and have taken over the main.dart code from the example expecting it to work (I have configured the PublicClientApplication slightly diferently though to accommodate to my B2C scenario)

final publicClientApp = PublicClientApplication(
    Configuration()
      ..auth = (BrowserAuthOptions()
        // Give MSAL our client ID
        ..clientId = clientId
        ..authority =
            "https://humbiapp.b2clogin.com/humbiapp.onmicrosoft.com/B2C_1_SignUpSignIn" // B2C Policy Authority
        ..knownAuthorities = ["humbiapp.b2clogin.com"] // Add your B2C domain here
        ..redirectUri = "https://localhost:8080/signin-oidc")
      ..system = (BrowserSystemOptions()
        ..loggerOptions = (LoggerOptions()
          ..loggerCallback = _loggerCallback
          // Log just about everything for the purpose of this demo
          ..logLevel = LogLevel.verbose)),
  );

The rest of the code is the same.

The web application starts.

The Flutter App

However, neither the popup nor the redirect login work as intended!

  1. In Redirect, when I click on the Google provider, the google mask shows up but it's "read-only". I can't input my address there, nothing happens upon typing.
  2. In Popup, I can login using Google Identity Provider and get accepted. However, upon receiving the token and the state on my redirect address, I get this:

The "page not found" upon getting the response

What am I missing? Is the MSAL.js library too old? Do I need some extra steps for the B2C with the redirect url?


Solution

  • I created a sample Flutter application with Azure AD B2C using MSAL.js and successfully logged in and out using Google as an identity provider.

    import 'package:flutter/material.dart';
    import 'package:msal_js/msal_js.dart';
    
    void main() {
      WidgetsFlutterBinding.ensureInitialized();
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      late PublicClientApplication pca;
      AccountInfo? _account;
      final String clientId = "<clientID>";
      final String authority = "https://<tenantName>.b2clogin.com/<tenantName>.onmicrosoft.com/<policyName>";
      final String redirectUri = "http://localhost:8080";
    
      @override
      void initState() {
        super.initState();
        _initializeMSAL();
      }
    
      void _initializeMSAL() {
        pca = PublicClientApplication(Configuration()
          ..auth = (BrowserAuthOptions()
            ..clientId = clientId
            ..authority = authority
            ..knownAuthorities = ["<tenantName>.b2clogin.com"]
            ..redirectUri = redirectUri)
          ..system = (BrowserSystemOptions()
            ..loggerOptions = (LoggerOptions()
              ..loggerCallback = (level, message, containsPii) {
                print("[MSAL] $message");
              }
              ..logLevel = LogLevel.verbose)));
    
        pca.handleRedirectFuture().then((result) {
          if (result != null) {
            print("[MSAL] Redirect successful.");
            setState(() {
              _account = result.account;
            });
          }
          _getCurrentUser();
        }).catchError((error) {
          print("[MSAL] Error handling redirect: $error");
        });
      }
    
      Future<void> _getCurrentUser() async {
        final accounts = await pca.getAllAccounts();
        if (accounts.isNotEmpty) {
          setState(() {
            _account = accounts.first;
          });
        }
      }
    
      Future<void> _loginPopup() async {
        try {
          final result = await pca.loginPopup(PopupRequest()
            ..scopes = ["openid", "offline_access"]);
          setState(() {
            _account = result.account;
          });
        } catch (e) {
          print("[MSAL] Login error: $e");
        }
      }
    
      Future<void> _loginRedirect() async {
        try {
          await pca.loginRedirect(RedirectRequest()
            ..scopes = ["openid", "offline_access"]);
        } catch (e) {
          print("[MSAL] Redirect login error: $e");
        }
      }
    
      Future<void> _logoutPopup() async {
        try {
          await pca.logoutPopup();
          setState(() {
            _account = null;
          });
        } catch (e) {
          print("[MSAL] Logout error: $e");
        }
      }
    
      Future<void> _logoutRedirect() async {
        try {
          await pca.logoutRedirect();
        } catch (e) {
          print("[MSAL] Redirect logout error: $e");
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: const Text("Flutter Web MSAL.js Demo")),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  if (_account == null) ...[
                    ElevatedButton(
                      child: const Text('Login Redirect'),
                      onPressed: _loginRedirect,
                    ),
                    ElevatedButton(
                      child: const Text('Login Popup'),
                      onPressed: _loginPopup,
                    ),
                  ],
                  if (_account != null) ...[
                    Text('Signed in as ${_account!.username}'),
                    ElevatedButton(
                      child: const Text('Logout Redirect'),
                      onPressed: _logoutRedirect,
                    ),
                    ElevatedButton(
                      child: const Text('Logout Popup'),
                      onPressed: _logoutPopup,
                    ),
                  ]
                ],
              ),
            ),
          ),
        );
      }
    }
    

    I have added the below URL in the service principle redirect URI under Mobile and desktop applications and enabled the Allow public client flows.

    http://localhost:8080
    

    enter image description here

    Output :

    I successfully logged in and logged out with Google provider.

    enter image description here

    enter image description here

    enter image description here

    enter image description here