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

How to listen for the callback in Flutter web? #88

Closed
EightRice opened this issue Sep 17, 2020 · 28 comments
Closed

How to listen for the callback in Flutter web? #88

EightRice opened this issue Sep 17, 2020 · 28 comments

Comments

@EightRice
Copy link

EightRice commented Sep 17, 2020

Neither uni_links nor webview_flutter support web. So how can you implement the listen method?
https://stackoverflow.com/questions/63946743/oauth2-in-flutter-web

@thosakwe
Copy link
Contributor

I'm fairly certain this package works on the Web, as it doesn't require dart:io or dart:mirrors.

You also don't need uni_links or webview_flutter on the Web, because the API's already exist in the browser.

If you call window.open(...), the child window can raise a CustomEvent in the parent window. I'm not sure how your server is set up, but whatever page begins the three-legged OAuth2 flow should be opened in a new window. The third-party will then redirect the user to the given redirect_url. From this redirect, you grab the code URL parameter, and use that to get a token. This token should then be passed to the parent window via an event.

The secure way to do this involves you writing the actual OAuth-related code on your server, having the client only worry about handling an event from a child window. If you don't have a server, you can do everything client-side, but if you're not doing an implicit grant, then you risk leaking your secret key.

@EightRice
Copy link
Author

@thosakwe . Using the html.window, you'd need separate routes to post and listen to the message, which is not happening because of flutter/flutter#33245 . You can't use # in the redirect uri.

@thosakwe
Copy link
Contributor

I don't think you would need separate routes, because the page that you open in a new window can just be a separate page that closes itself after firing an event in the parent, and won't need Flutter, or really much in the way of presentation, as it's an intermediate step.

I haven't used Flutter Web, but I'm imagining you can listen for events while using it.

@abahnj
Copy link

abahnj commented Jan 14, 2021

@EightRice Did you ever figure out a solution for this?

@helloURVA
Copy link

Yes, this is in high demand! We'd love to know a way out too.

@RasmusSlothJensen
Copy link

RasmusSlothJensen commented Jan 30, 2021

My solution was to forked the repo and changed AuthorizationCodeGrant class so it is possible to store it while the browser is doing the round-trip to the idp.
I pass the authorization URL to window.location.attach() and on the callback I deserialize the AuthorizationCodeGrant i stored and then I removed the hash from Uri.base and passed the queryParameters to handleAuthorizationResponse

@robinjanke
Copy link

Hey @RasmusSlothJensen can you please make your fork public? So anyone can use this package with flutter for web. Thanks!

@RasmusSlothJensen
Copy link

Hi @robinjanke
Here is the link:
https://github.com/Triple-Arm-Technique-ApS/oauth2
I have not maintained it, but the change is fairly small so I hope you can use still it :)

@robinjanke
Copy link

Hi @RasmusSlothJensen , big thanks for sharing this! :)
Can you provide me an example how you have implemented this on your app?
Im stuck at doing something like window.location.assign(authorizationUrl.toString()); but cant listen for changes in the url..

@RasmusSlothJensen
Copy link

RasmusSlothJensen commented Mar 2, 2021

Hi @robinjanke

I have a lot of abstraction in my app to support both mobile and web.
But I can show how I catch the callback.
I don't know it is the correct way of doing it but I use the MaterialApp widget and in the onGenerateRoute callback I catch the callback from the idp:

onGenerateRoute: (RouteSettings settings) {
  if (settings.name.startsWith('/callback')) {
    return MaterialPageRoute(
      builder: (context) => CallbackHandlerPage(
        callback: Uri.base,
      ),
    );
   ...
  }

In the CallbackHandlerPage I retrieve the AuthorizationCodeGrant from local storage and call

final client = await grant.handleAuthorizationResponse(
            callback.queryParameters,
          );

@ghost
Copy link

ghost commented Mar 3, 2021

@RasmusSlothJensen Wow, thanks for sharing this with us. Love it! 👏

@robinjanke
Copy link

@RasmusSlothJensen Thanks for your fast answer, i already got an step further.
Can you also tell me how you managed to save the AuthorizationCodeGrant in the local storage? I tried it with getIt like the following way: getIt.registerSingleton<oauth2.AuthorizationCodeGrant>(grant); but got the following error when accessing it insied the onGenerateRoute function using getIt.get<oauth2.AuthorizationCodeGrant>(): "Object/factory with type AuthorizationCodeGrant is not registered inside GetIt. I also used your fork

@RasmusSlothJensen
Copy link

@robinjanke I use https://pub.dev/packages/shared_preferences for web.
When I create the Authorization URL I store the AuthorizationCodeGrant

  final grant = AuthorizationCodeGrant(
        clientID,
        authorizationEndpoint,
        tokenEndpoint,
      );
   final authorizationUrl =
        grant.getAuthorizationUrl(redirectUri, scopes: scopes);
   final String json = jsonEncode(
      grant.toJson(),
   );
   await _sharedPreferences.setString('grant', json);

Then when I need to handle the callback I get the grant from storage.

final json = _sharedPreferences.getString('grant');
final grant = AuthorizationCodeGrant.fromJson(
        jsonDecode(json),
      );

@giiyms
Copy link

giiyms commented Mar 8, 2021

Any chance someone can post a full solution test project?
I must be missing something.

@robinjanke
Copy link

@giiyms I am gonna write an medium article for that and post an link here when it is finished :)

@robinjanke
Copy link

@giiyms
Copy link

giiyms commented Mar 9, 2021

@giiyms I hope that this will help you: https://robinjanke1.medium.com/oauth2-with-flutter-web-e7a2b0dac7f3

@robinjanke Nice. This does the job, thanks!

My only annoyance is that the API I am using automatically switches to https://localhost:8080/callback.html instead of http://localhost:8080/callback.html so debugging locally is a pain. Did you run into that?

@robinjanke
Copy link

@giiyms You can use something like ngrok to handle this or you can get an free .tok domain and use this for locat development

@RasmusSlothJensen
Copy link

@robinjanke nice article.
I recently updated my navigation to use RouterDelegate and found it easy to handle the callback with this approach, I followed this article: https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade.

@chessasuke
Copy link

@robinjanke nice article.
I recently updated my navigation to use RouterDelegate and found it easy to handle the callback with this approach, I followed this article: https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade.

Nice!, would u mind if I ask how did u get it to work, with Nav 2.0

@RasmusSlothJensen
Copy link

Hi @chessasuke

I created a small sample to demonstrate how I got it working. The code is not nice at all, but I think it shows how it can be done.
https://github.com/RasmusSlothJensen/oauth_sample

@chessasuke
Copy link

Hi @chessasuke

I created a small sample to demonstrate how I got it working. The code is not nice at all, but I think it shows how it can be done.
https://github.com/RasmusSlothJensen/oauth_sample

Many thanks!! It worked like a charm and cleared all my doubts

@EightRice
Copy link
Author

Glad to see this got so much attention. Seems like it has been successfully addressed, so closing the issue.

@vicdotdevelop
Copy link

Any news on this? Seems like the fork is not available anymore.
I guess to persist the grant is the only approach to get this running on flutter web right?
There is still no fromJson & toJson on the grant as well as I cannot store the code verifier and inject it in a new grant object.

So any suggestions? This drives me crazy... 🤕

@RasmusSlothJensen
Copy link

RasmusSlothJensen commented Apr 5, 2022

Hi @vicdotdevelop

What I ended up doing was opening an external browser window to handle the roundtrip to the idp, which means the application didn't lose the state and you would avoid the reload of the app when handling the callback.

I made a sample (not pretty) for you to checkout if interested https://github.com/RasmusSlothJensen/flutter_oauth_external_window

@vicdotdevelop
Copy link

Hi @vicdotdevelop

What I ended up doing was opening an external browser window to handle the roundtrip to the idp, which means the application didn't lose the state and you would avoid the reload of the app when handling the callback.

I made a sample (not pretty) for you to checkout if interested https://github.com/RasmusSlothJensen/flutter_oauth_external_window

@RasmusSlothJensen thanks for the fast reply, I will try it today and let u know if you saved my life 😁

@yehya-qassim
Copy link

You can check the solution i created for such case.
the issue and the solution

@markbeij
Copy link

@RasmusSlothJensen, it's not required to persist the whole AuthorizationCodeGrant if you supply your own verifyCode. Then you only have to persist the verifyCode.

This code can be used to generate a code yourself (got it from oauth2 package)

  /// Allowed characters for generating the _codeVerifier
  static const String _charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';

  // Randomly generate a 128 character string to be used as the PKCE code
  // verifier.
  static String createCodeVerifier() => List.generate(
        128,
        (i) => _charset[Random.secure().nextInt(_charset.length)],
      ).join();

Then in onGenerateRoute you can create the grant with the same settings and call grant.handleAuthorizationResponse(...). Only thing is that you have to call grant.getAuthorizationUrl(...) first.

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

No branches or pull requests