Giter VIP home page Giter VIP logo

aad_b2c_webview's Introduction

Azure AD B2C Embedded Webview

Azure AD B2C Embedded Webview is a very simple Flutter package that demonstrates how to use the embedded web view to sign in users with Azure AD B2C. Currently, using Flutter packages - appAuth and flutter_azure_b2c redirects to browser and doesn't provide in-app experience.

This package embeds the web view of the user flow endpoint using flutter_appview and redirects the user as per onRedirect callback method.

Features

Embedded web view for Azure AD B2C for providing in-app experience Redirects to the route specified in redirectRoute after successful sign in Successfully secures the id token or access token in the app using flutter secure storage Navigates to screen in app after successful sign in

Getting started

To use the package in your Flutter app, add the following code to your main.dart file:

dependencies:
  aad_b2c_webview: <latest_version>

Usage

Simple In-built Sign In Button

To add the easy to use sign in with microsoft button simply use the AADLoginButton widget and a beautiful sign in button appears as shown below.

Add the following /android/app/src/main/AndroidManifest.xml inside the tags:

<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="myurl.com" />
</intent-filter>

Change the redirect URL in our flutter code to https://myurl.com/myappname and add this as a redirect URL in the Azure AD B2C project

Our updated version of the main.dart file is now as follows:

import 'package:aad_b2c_webview/aad_b2c_webview.dart';
import 'package:flutter/material.dart';

import 'counterdemo.dart';

void main() {
  runApp(const MyApp());
}

onRedirect(BuildContext context) {
  Navigator.of(context).pushNamedAndRemoveUntil('/myappname', (Route<dynamic> route) => false);
}

class MyApp extends StatelessWidget {

  const MyApp({super.key});

  // This widget is the root of your application.
  @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',
          ),
          bodyLarge: TextStyle(
            color: Color(0xFF8A8A8A),
            fontSize: 17,
            fontWeight: FontWeight.w400,
            fontFamily: 'UberMoveText',
          ),
          displayMedium: 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 LoginPage(),
        '/myappname': (context) => const CounterDemo(),
      },
    );
  }
}

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  String? jwtToken;
  String? refreshToken;

  @override
  Widget build(BuildContext context) {
    const aadB2CClientID = "<app-id>";
    const aadB2CRedirectURL = "https://myurl.com/myappname";
    const aadB2CUserFlowName = "B2C_1_APPNAME_Signin";
    const aadB2CScopes = ['openid', 'offline_access'];
    const aadB2TenantName = "<tenantName>";
    const aadB2CUserAuthFlow =
        "https://$aadB2TenantName.b2clogin.com/$aadB2TenantName.onmicrosoft.com";

    return Scaffold(
      appBar: AppBar(
        title: const Text("AAD B2C Login"),
        backgroundColor: const Color(0xFF2F56D2)
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            /// Login flow
            AADLoginButton(
              userFlowUrl: aadB2CUserAuthFlow,
              clientId: aadB2CClientID,
              userFlowName: aadB2CUserFlowName,
              redirectUrl: aadB2CRedirectURL,
              context: context,
              scopes: aadB2CScopes,
              onAnyTokenRetrieved: (Token anyToken) {},
              onIDToken: (Token token) {
                jwtToken = token.value;
              },
              onAccessToken: (Token token) {
              },
              onRefreshToken: (Token token) {
                refreshToken = token.value;
              },
              onRedirect: (context) => onRedirect(context),
            ),

            /// Refresh token

            TextButton(
              onPressed: () async {
                if (refreshToken != null) {
                  AzureTokenResponse? response =
                  await ClientAuthentication.refreshTokens(
                    refreshToken: refreshToken!,
                    tenant: aadB2TenantName,
                    policy: aadB2CUserAuthFlow,
                    clientId: aadB2CClientID,
                  );
                  if (response != null) {
                    refreshToken = response.refreshToken;
                    jwtToken = response.idToken;
                  }
                }
              },
              child: const Text("Refresh my token"),
            ),
            TextButton(
              onPressed: () async {
                Navigator.pushNamed(context, '/myappname');
              },
              child: const Text("Go To Counter Demo"),
            )
          ],
        ),
      ),
    );
  }
}

Custom Sign in

Simply call page direct or use custom build sign in button to call web view page

import 'package:aad_b2c_webview/aad_b2c_webview.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class MyLoginPage extends StatelessWidget {
  const MyLoginPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const aadB2CClientID = "<clientId>";
    const aadB2CRedirectURL = "<azure_active_directory_url_redirect>";
    const aadB2CUserFlowName = "B2C_<name_of_userflow>";
    const aadB2CScopes = ['openid', 'offline_access'];
    const aadB2CUserAuthFlow =
        "https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com"; // https://login.microsoftonline.com/<azureTenantId>/oauth2/v2.0/token/
    const aadB2TenantName = "<tenant-name>";

    return Scaffold(
      body: ADB2CEmbedWebView(
        tenantBaseUrl: aadB2CUserAuthFlow,
        userFlowName: aadB2CUserFlowName,
        clientId: aadB2CClientID,
        redirectUrl: aadB2CRedirectURL,
        scopes: aadB2CScopes,
        onAnyTokenRetrieved: (Token anyToken) {},
        onIDToken: (Token token) {},
        onAccessToken: (Token token) {},
        onRefreshToken: (Token token) {},
      ),
    );
  }
}

Send Optional Parameters

In the AADLoginButton widget, you have the flexibility to include optional parameters in order to customize the URL according to your specific requirements. The optionalParameters parameter allows you to pass a list of OptionalParam objects, where each object consists of a key and a value that represent the parameter name and its corresponding value, respectively.

Here's an example of how you can utilize the optionalParameters parameter to add new parameters to the URL:

optionalParameters: [
  OptionalParam(key: "parameter1", value: "value1"),
  OptionalParam(key: "parameter2", value: "value2"),
],

In the above example, we include two optional parameters: "parameter1" with the value "value1" and "parameter2" with the value "value2". You can include multiple OptionalParam objects within the list to incorporate multiple optional parameters in the URL.

These optional parameters provide you with the ability to customize the behavior of the URL and conveniently transmit additional information as per your needs.

Please adapt the example to suit your specific use case or requirements.

aad_b2c_webview's People

Contributors

baxi19 avatar craig-waite avatar dependabot[bot] avatar filip8600 avatar fullstackweb-developer avatar jchirinosodio avatar jnelle avatar microsoftopensource avatar mohanajuhi166 avatar mpp-marcink avatar sleeknoah avatar szczygieldev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

aad_b2c_webview's Issues

Not getting access token

I am always getting access token as null, I can able to get IDToken and RefreshToken.

Could you please assist to resolve this issue

Tasks

Library stops working after 0.0.51 for Android

Good evening,

I was using the 0.0.51 version and it works fine on Android.
To make it work on iOS I had to upgrade it but now the same stopped working on Android.

After some tests I noticed iOS starts working after version 0.0.53 while Andrid stops working after 0.0.51

My implementation is pretty much linear

return ADB2CEmbedWebView( loadingReplacement: const Loading(), optionalParameters: const [], tenantBaseUrl: aadB2CUserAuthFlow, userFlowName: aadB2CUserFlowName, clientId: aadB2CClientID, redirectUrl: aadB2CRedirectURL, onRedirect: (context) => Loading.modalLoading(), scopes: aadB2CScopes, onAnyTokenRetrieved: (Token anyToken) { ... }, onIDToken: (Token token) { ... }, onAccessToken: (Token token) { ... }, onRefreshToken: (Token token) { ... }, );

Debugging inside the library the errors seems to be the following:

Response ({"error":"invalid_request","error_description":"AADB2C90117: The scope 'openid%20offline_access%20profile%20https://myWebSite.onmicrosoft.com/exm.be.webapi/access_as_user' provided in the request is not supported.\r\nCorrelation ID: 53dc1c3f-a814-428c-8adf-a3153d5eb8b3\r\nTimestamp: 2024-03-07 13:24:06Z\r\n"})

It's strange the scopes are the problem, because on iOS the login is working fine and on Android it's working until 0.0.52

Do I have to modify something when upgrading over 0.0.51 for Android to work?

Bug with login using web view - method call in the wrong place

Hi, first thanks for creating this library, great work and it's saving some time for us :)
After I tried to implement the web view in my app, I found issues related to it. I will try to explain it in detail and I hope you will not mind because I want to help you improve this package.

So you have the method onPageFinishedTasks(URL, response); that is being called inside of onPageFinished. That method for checking the URL is called only once when the login screen is initially loaded, and that's it. If I input the credentials for login nothing happens, because onPageFinished is calling onPageFinishedTasks(URL, response) only once, and that's the case when the login screen is being loaded.
Further debugging leads me to the conclusion that the right place to call the method onPageFinishedTasks(URL, response); is inside of navigationDelegate property because that field is being triggered every time the user interacts with the screen and tries to log in.
I tried this solution in my app and it's working perfectly, also I have tried your's and it's not working. If you have any questions or maybe I was not clear enough please text me I am open to help :)

Redirecting on IOS

Hi guys, i have issue with redirection after login on IOS. Tried different redirect URI's and nothing is happening. In logs on azure I can see that user is successfully log in, but after i click Sign in button nothing is happening. Any idea how to solve redirection issue? Thank you

Not compatible with webview_flutter ^4

I would like to know if support for webview_flutter ^4 is on the current roadmap.
If not, can I contribute a new version that supports webview_flutter ^4?

Tasks

Add Support For Enable Keep Me Signed In Flow - Flutter Username Password autofill from next session if already added remember me option

Hey @mohanajuhi166 @microsoftopensource @Baxi19 @Sleeknoah

I was recently wanted to add the feature option to enable keep me signed in feature using microsoft b2c login, But I don't find any support using this package. I've added changes to B2C policy as guided in the following official blog,
https://github.com/azure-ad-b2c/unit-tests/tree/main/session#enable-keep-me-signed-in-kmsi

So can you please help adding support from flutter side keep me signed in feature using this package?

Thanks

Google login error that says โ€œ403 disallowed_useragent,โ€

hi there,

I configured and tested the Azure AD B2C setup for Microsoft and Google logins. It worked.
When i use the ADB2CEmbedWebView from my Android code, Microsoft login works, but Google login errors out with โ€œ403 disallowed_useragent,โ€.

On google's support website, it clearly states that "_Embedded WebViews puts your security at risk because they could let third parties access and change communications between you and Google.".

update: 7/3/24 - After enabling Facebook access, get the same message "logging in to Facebook from an embedded browser is disabled".

Does this mean its not a good practice to use Embedded webviews? @mohanajuhi166 would be keen to get your thoughts

Not compatible with the latest version of flutter

Thanks for making this useful package.
But I can't use this with flutter 3.0.5

The lower bound of "sdk: '>=2.3.0 <3.0.0'" must be 2.12.0 or higher to enable null safety.

I really want to use this package in my project, is there any solution?
Please let me know how I can contribute to this repository to make this possible.

Tasks

No tasks being tracked yet.

ResponseType as default / code throws error

Hallo everybody,
trying to Login with ADB2CEmbedWebView using responseType: Constants.default or authCode (which is the same) throws Dio exception.
responseType: idToken or accessToken works fine.

It comes to my understanding that the authCode is used to implement the UserFlow login and call and extract both idToken and refreshToken.

Thanks in advance and best regards.

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [bad response]: This exception was thrown because the response has a status code of 400 and RequestOptions.validateStatus was configured to throw for this status code. E/flutter (11937): The status code of 400 has the following meaning: "Client error - the request contains bad syntax or cannot be fulfilled" E/flutter (11937): Read more about status codes at https://developer.mozilla.org/en-US/docs/Web/HTTP/Status E/flutter (11937): In order to resolve this exception you typically have either to verify and fix your request code or you have to fix the server code. E/flutter (11937): E/flutter (11937): #0 DioMixin.fetch.<anonymous closure> (package:dio/src/dio_mixin.dart:507:7) E/flutter (11937): #1 _FutureListener.handleError (dart:async/future_impl.dart:180:22) E/flutter (11937): #2 Future._propagateToListeners.handleError (dart:async/future_impl.dart:858:47) E/flutter (11937): #3 Future._propagateToListeners (dart:async/future_impl.dart:879:13) E/flutter (11937): #4 Future._completeError (dart:async/future_impl.dart:655:5) E/flutter (11937): #5 _SyncCompleter._completeError (dart:async/future_impl.dart:63:12) E/flutter (11937): #6 _Completer.completeError (dart:async/future_impl.dart:27:5) E/flutter (11937): #7 Future.any.onError (dart:async/future.dart:618:45) E/flutter (11937): #8 _RootZone.runBinary (dart:async/zone.dart:1666:54) E/flutter (11937): #9 _FutureListener.handleError (dart:async/future_impl.dart:177:22) E/flutter (11937): #10 Future._propagateToListeners.handleError (dart:async/future_impl.dart:858:47) E/flutter (11937): #11 Future._propagateToListeners (dart:async/future_impl.dart:879:13) E/flutter (11937): #12 Future._completeError (dart:async/future_impl.dart:655:5) E/flutter (11937): #13 Future._asyncCompleteError.<anonymous closure> (dart:async/future_impl.dart:745:7) E/flutter (11937): #14 _microtaskLoop (dart:async/schedule_microtask.dart:40:21) E/flutter (11937): #15 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5) E/flutter (11937):

Tokens returned but App does not redirect properly

Hi,

I'm having trouble even getting the very basic demo app to work correctly. The problem I'm having is that I can log in to AAD B2C (and I can see from the network traffic that the tokens are all coming back correctly) via the plugin code provided, but once I'm logged in I get the following error:

image

I am currently only trying to get the flutter demo counter page hidden behind a login screen. Below is my main.dart file. The only other change I've made is to add manifestPlaceholders += [ appAuthRedirectScheme: 'com.my.app.name' ] to the defaultConfig section of the /android/app/build.gradle file.

Any help you could provide would be amazing as we've been going round in circles on this a for a couple of days now.

import 'package:aad_b2c_webview/aad_b2c_webview.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

onRedirect(BuildContext context) {
  Navigator.pushNamed(context, '/');
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      initialRoute: '/',
      routes: {
        '/': (context) => const MyLoginPage(),
        '/oauthredirect/': (context) => const MyHomePage(title: 'Flutter Demo Redirect Route')
      },
    );
  }
}

class MyLoginPage extends StatelessWidget {
  const MyLoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    const aadB2CClientID = "################################";
    const aadB2CRedirectURL = "com.my.app.name://oauthredirect/";
    const aadB2CUserFlowName = "B2C_1_MyAppName_Signin";
    const aadB2CScopes = ['openid', 'offline_access'];
    const aadB2CUserAuthFlow =
        "https://<tenantName>.b2clogin.com/<tenantName>.onmicrosoft.com";
    const aadB2TenantName = "<tenantName>";
    return Scaffold(
      body: ADB2CEmbedWebView(
        tenantBaseUrl: aadB2CUserAuthFlow,
        userFlowName: aadB2CUserFlowName,
        clientId: aadB2CClientID,
        redirectUrl: aadB2CRedirectURL,
        scopes: aadB2CScopes,
        onAnyTokenRetrieved: (Token anyToken) {},
        onIDToken: (Token token) {},
        onAccessToken: (Token token) {},
        onRefreshToken: (Token token) {},
        optionalParameters: [],
        onRedirect: (context) => const MyHomePage(title: 'Flutter Demo On Redirect'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Please help on React Native b2c login

I need MSAL plugin for react native

Tasks

No tasks being tracked yet.

Redirect uri

Hi,

First of all thanks for this library

I have implemented the code given in the example in pubdev. After pressing the button, I am able to receive in the callbacks of the widget (onIDToken and onAnyTokenRetrieved) receiving correctly the token given by B2c auth. The callback onRedirect is also being call correctly and i am able to navigate to another screen (my home screen). My only issue, is that after logging in the browser can't find the redirect url of the schema, the message shown in the browser of the mobile phone after login is: .com.example/authapp://oauthredirect/?code........ could not be loaded because net::ERR_UNKNOWN_URL_SCHEME

I had created (in android) a schema in the manifest adding in the activity tag

<intent-filter>
   <action android:name="android.intent.action.VIEW" />

   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />

   <data
       android:scheme="com.example.authapp"
       android:host="oauthredirect"
       />
</intent-filter>

I tried to handle the url by installing the library uni_links and adding a listener in the main function :

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    initUniLinks();

    // TODO: implement initState
    super.initState();
  }

  void initUniLinks() async {
    getInitialLink().then((String? link) {
      if (link != null) {
        // Handle the link
      }
    });

    linkStream.listen((String? link) {
      if (link != null &&
          link.startsWith('com.example.authapp://oauthredirect')) {
        final Uri uri = Uri.parse(link);
        final String code = uri.queryParameters['code']!;
        // Now you have the code, proceed with your authentication flow
      }
      if (link != null) {
        // Handle the link
      }
    }, onError: (err) {
      // Handle the error
    });
  }

But when I debug the code, the breakpoint in the listener (breakpoint in line if (link != null &&
link.startsWith('com.example.authapp://oauthredirect')) ) is never reached.

I am a little bit lost what I need to do in order to show instead of a broken url text a loading progress or something like that during this fraction of second between the the token retrieved and the redirect callback that is sending the user to the home page.

Thanks.

Not able to logout user for azure b2c login

Hey @mohanajuhi166 @microsoftopensource I was trying this official flutter package following is my code for azure b2c login.

 const aadB2CClientID = 'random-95eb-val-a09b-token';
    const aadB2CRedirectURL = 'https://login.live.com/oauth0_desktop.srf';
    const aadB2CUserFlowName = 'B2C_1A_signin_Mobile_Dev';
    const aadB2CScopes = ['openid', 'offline_access'];
    const aadB2TenantName = 'testTenantName';
    const aadB2CUserAuthFlow = 'https://<aadB2TenantName>.b2clogin.com/tfp/<aadB2TenantName>.onmicrosoft.com/';
    
    return Scaffold(
      body: ADB2CEmbedWebView(
        tenantBaseUrl: aadB2CUserAuthFlow,
        userFlowName: aadB2CUserFlowName,
        clientId: aadB2CClientID,
        redirectUrl: aadB2CRedirectURL,
        scopes: aadB2CScopes,
        onAnyTokenRetrieved: (Token anyToken) {},
        onIDToken: (Token token) {
          log('idToken==> ${token?.value ?? ''}');
        },
        onAccessToken: (Token token) {
           accessToken = token.value;
          log('accessToken==> ${accessToken ?? ''}');
        },
        onRefreshToken: (Token token) {
          refreshToken = token.value;
          log('refreshToken==> ${refreshToken ?? ''}');
        },
        optionalParameters: const [],
        onRedirect: (context) {
          log('accessToken==> ${accessToken ?? ''}');
          log('refreshToken==> ${refreshToken ?? ''}');

          return {
                  // Push user to new screen.
          };
        },
      ),
    );

The whole flow is working perfectly fine, i'm getting accesstoken and refreshtoken both. So how can I manage the session for this user? How to logout user?

Can you please provide some solution on this?

Thanks.

Tasks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.