Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented deep link and local server options instead web_auth #74

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

TheOnlyBeardedBeast
Copy link

@TheOnlyBeardedBeast TheOnlyBeardedBeast commented Dec 3, 2024

Summary

This PR replaces web_auth library with url_launcher and app_links and local server options, this setup gives the user more control, also this provides a cross-platform solution (all this started because I needed desktop support).

Edit: the url_launcher could handle an issue I saw in the issues regarding forcing a page to open in an external browser, with this PR this would be now possible.
Also before I did everything you see here, I tried to implement flutter_web_auth_2 I had ugly java kotlin compatibility issues which I wasnt able to handle, but I am not good with android configuration. The dart part was almost a drop in replacement tho.

There are 2 callback strategies:

  • scheme strategy: to listen for a custom scheme, like the scheme predefined in the logto config io.logto//callback
  • localserver strategy: instead of listening to a given custom scheme callback it launches a webserver on a user defined port, the local server closes after it gets the callback.

I am using GetIt for dependency injection and here is my extension method to register differen strategies (note: app id is also conditional in the config because one app is a flutter app listening to the logto scheme callback, the second is a react app listening to http callback). If this PR with modifications would go through it would be nice to allow more options for custom schemes?

extension AuthenticationExtension on GetIt {
  void registerAuthentication(){
    if(Platform.isAndroid || Platform.isIOS)
    {
      registerSingleton<LogtoClient>(LogtoClient(
      config: config,
      httpClient: http.Client(),
      callbackStrategy: SchemeStrategy()
    ));
    } else {
      registerSingleton<LogtoClient>(LogtoClient(
      config: config,
      httpClient: http.Client(),
      callbackStrategy: LocalServerStrategy(3011)
    ));
    }
  }
}

Additional changes for better usability, a stream was added to the client to notify user about athentication changes, so there will be no additional state widget needed. This needs initialization for that an init() method was also added to the client. Example usage in the standard template for flutter app:

void main() {
  // initialization of the GetIt dependency injection library where the logto client is registered as a singleton
  configureDependecies();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final LogtoClient logto = GetIt.I<LogtoClient>();

  MyApp({super.key});

  // This widget is the root of your application.

  @override
  Widget build(BuildContext context) {
    logto.init();
     
    return MaterialApp(
    ...

Using the stream and listen to authentication changes in an example AuthGuard widget:

class AuthGuard extends StatelessWidget {
  final Widget authenticatedWidget;
  final Widget? unauthenticatedWidget;
  final Widget loadingWidget;

  const AuthGuard({
    super.key,
    required this.authenticatedWidget,
    this.unauthenticatedWidget,
    this.loadingWidget = const CircularProgressIndicator(),
  });

  @override
  Widget build(BuildContext context) {
    final logto = GetIt.I<LogtoClient>();

    return StreamBuilder<bool>(
      stream: logto.isAuthenticatedStream,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: loadingWidget);
        }

        final isAuthenticated = snapshot.data ?? false;

        if (isAuthenticated) {
          return authenticatedWidget;
        } else {
          return unauthenticatedWidget ?? const SizedBox.shrink();
        }
      },
    );
  }
}
AuthGuard(authenticatedWidget: Column(children: [
              const Text("You are authenticated"),
              TextButton(
              onPressed: () async {
                await logto.signOut();
              },
              child: const Text('Sign Out'),
            )
            ],), unauthenticatedWidget: Column(children: [
              const Text("You are not authenticated"),
              TextButton(
              onPressed: () async {
                await logto.signIn(Platform.isAndroid || Platform.isIOS ? "io.logto://my.app.callback" : "http://localhost:3011");
              },
              child: const Text('Sign In'),
            )
            ],),)

Testing

I tested this by directly replacing the logto_dart_sdk with the forked and changed version locally. I run the application which I am building in an android emulator and on ubuntu as a dektop app. I tested the local server options on both devices and the scheme option on andoid only, on android the URL launcher also used a webview directly from the app.

Checklist

Nothing from these at this point, I would like to use this PR for further discussion, future options and feedback.

  • [-] .changeset
  • [-] unit tests
  • [-] integration tests
  • [-] necessary TSDoc comments

@TheOnlyBeardedBeast
Copy link
Author

Added an option for the callback strategy to force external application (browser) opening if possible.

@TheOnlyBeardedBeast TheOnlyBeardedBeast marked this pull request as ready for review December 5, 2024 09:15
@simeng-li
Copy link
Contributor

Thank you for your support. Will take a look soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants