LinusU / flutter_web_auth

Flutter plugin for authenticating a user with a web service
MIT License
198 stars 176 forks source link

Can't get web auth to work with my own authentication server #154

Open JPFrancoia opened 1 year ago

JPFrancoia commented 1 year ago

Hello,

I have my own authentication service to authenticate my users. The service is written in Go. The authentication flow works like this:

The above works. I'm now trying to build the UI. So far I have something like this:

import 'package:flutter/material.dart';
import 'package:social_login_buttons/social_login_buttons.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample',
      home: LoginPage(),
    );
  }
}

class LoginPage extends StatelessWidget {
  LoginPage({super.key});

  @override
  Widget build(BuildContext context) {

    return LayoutBuilder(builder: (context, constraints) {
      return Scaffold(
        body: SocialLoginButton(
          buttonType: SocialLoginButtonType.facebook,
          onPressed: () async {
            final result = await FlutterWebAuth.authenticate(
                url: "http://localhost:8080/auth/facebook", callbackUrlScheme: "callback-scheme");

            print("result: $result");

            final token = await Uri.parse(result).queryParameters['token'];
            print(token);
          },
        ),
      );
    });
  }
}

On the server side, this is the implementation of the /auth/callback endpoint:

func AuthCallback(c *gin.Context) {
    user, err := gothic.CompleteUserAuth(c.Writer, c.Request)

        ...

    c.HTML(http.StatusOK, "auth.html", gin.H{})
    // c.Redirect(http.StatusFound, "callback-scheme://")
}

I tried serving the auth.html page mentioned here from the server. With this setup, I can click the Facebook button, the web page opens, I can login on Facebook, and I can give permissions to the app. I'm then redirected to the auth.html page, and then...nada. I never get back anything for final result =... (on the client side). There is no error in the flutter console.

I tried other stuff on the server side:

    c.JSON(http.StatusOK, gin.H{"token": "hello"})

With this, I ultimately a json blob with the hello token in the web page that opens, but I'm not getting anything back on the client side.

I'm missing something, but I don't know what. Would you be able to help please?

LinusU commented 1 year ago

The auth.html page is if you are using Flutter Web and is not for iOS/Android.

c.Redirect(http.StatusFound, "callback-scheme://")

This looks like the correct approach to me, could it possibly be that there needs to be something more in the url? Could you try changing this to something like:

c.Redirect(http.StatusFound, "callback-scheme://success")

Also, which platform are you targeting?

JPFrancoia commented 1 year ago

The auth.html page is if you are using Flutter Web

Yes, I understood that. For now I'm testing the setup with chrome, so I went for the web approach, but I'm not getting anything back for result.

Also, which platform are you targeting?

Ideally, all of them :)

I'm going to try on android just now (it's not as easy as the web approach because of the networking)

LinusU commented 1 year ago

The auth.html page is if you are using Flutter Web

Yes, I understood that. For now I'm testing the setup with chrome, so I went for the web approach, but I'm not getting anything back for result.

Ah, I see. Unfortunately I haven't personally tested the web setup much, it was contributed by someone else. Could you try the example app in this repo and see if that works for you?

JPFrancoia commented 1 year ago

Ok it also doesn't work for Android (we can focus on this platform for now if you have worked with it more).

Server:

func AuthCallback(c *gin.Context) {
    user, err := gothic.CompleteUserAuth(c.Writer, c.Request)

        ...

    c.Redirect(http.StatusFound, "callback-scheme://success")
}

Client:

            final result = await FlutterWebAuth.authenticate(
                url: url, callbackUrlScheme: "callback-scheme");

            print("result: $result");

What I see on the phone:

I'm not sure how it should work here, if auth/callback redirects to callback-scheme://success, where do I send the JWT token back?

Should I do something like this on the server side:

func AuthCallback(c *gin.Context) {
    user, err := gothic.CompleteUserAuth(c.Writer, c.Request)

        ...
    // httpOnly MUST be set to true for security
    c.SetCookie(
        "refresh_token",
        refreshToken,
        int(VALIDITY_REFRESH_TOKEN.Seconds()),
        "/",
        HOST,
        false,
        true,
    )
    c.Redirect(http.StatusFound, "callback-scheme://success")
}
JPFrancoia commented 1 year ago

Ok I got it working on Android. My mistake was to generate the oauth URL and returning it as a json payload, instead of just redirecting to the url:

func Login(c *gin.Context) {
        ...
    url, err := gothic.GetAuthURL(c.Writer, c.Request)

    if err != nil {
        c.AbortWithError(http.StatusInternalServerError, err)
    }

    // c.JSON(http.StatusOK, gin.H{"redirect": url}) // NOT GOOD
    c.Redirect(http.StatusFound, url)
}

The rest is unchanged.

Now unfortunately this doesn't work with web as is.

JPFrancoia commented 1 year ago

Could you try the example app in this repo and see if that works for you?

I tried it. I don't think it works. First, I needed to run with flutter run -d web-server --web-port=43823 --web-hostname=127.0.0.1, because otherwise I'm getting the error Error: Unsupported operation: ServerSocket.bind. This is because the example app uses dart:io, which isn't available in web (see: https://stackoverflow.com/questions/62935688/unsupported-operation-at-serversocket-bind-using-httpserver-in-flutter)

I got it to run (no error, authentication page displaying properly in browser), when I click the auth button I'm redirected to the expected url but I don't see the result of result = await FlutterWebAuth.authenticate.