Using SignalR in ASP.NET Core 6 & Xamarin.Forms with Firebase and Google as a Auth provider (Part 2: Android)

Using SignalR in ASP.NET Core 6 & Xamarin.Forms with Firebase and Google as a Auth provider (Part 2: Android)
πŸ’‘
This is second part of one huge blog post! This part will only cover Android development, for Server and iOS click here!

tl dr; give me repo? Yes siiir πŸ™

GitHub - jeremi-przeorek/GC-SignalR: Repository for blog post
Repository for blog post. Contribute to jeremi-przeorek/GC-SignalR development by creating an account on GitHub.

☁ Previously on "Lost"...

Last time we created server side app with Firebase Authentication, we did some amazing designs for app and also implemented iOS version of our Xamarin app that will use Firebase Authorization and Google as Auth provider. Only Android is missing...

Okaaay Let's go

So we're here! πŸ‘‡ This is how our main page looks like.

IGoogleAuth for πŸ€–

If we'll click on the button obviously it will... crash, because we haven't implemented IGoogleAuth interface from previous post for Android.

    public interface IGoogleAuth
    {
        OAuth2Authenticator Auth { get; }
        event EventHandler<AuthenticatorCompletedEventArgs> AuthSuccess;
        event EventHandler<AuthenticatorCompletedEventArgs> AuthFailure;
        void LoginAsync();
    }

First of all you need to add Xamarin.Auth NuGet package to our Android project. Just like on the iOS 😊

Next... Create new class called GoogleAuth.cs that implements mentioned above interface in the Android project.

using Android.Content;
using System;
using Xamarin.Auth;

namespace GC.SgnlR.Droid
{
    internal class GoogleAuth : IGoogleAuth
    {
        public OAuth2Authenticator Auth { get; private set; }

        public event EventHandler<AuthenticatorCompletedEventArgs> AuthSuccess;
        public event EventHandler<AuthenticatorCompletedEventArgs> AuthFailure;

        public void LoginAsync()
        {
            Auth = new OAuth2Authenticator(
                clientId: "Put_ClientId_Here",
                clientSecret: "",
                scope: "profile",
                authorizeUrl: new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
                redirectUrl: new Uri("Put_RedireceUrl_Here"),
                accessTokenUrl: new Uri("https://www.googleapis.com/oauth2/v4/token"),
                isUsingNativeUI: true);

            Auth.Completed += Auth_Complted;

            var activity = Xamarin.Essentials.Platform.CurrentActivity;

            Intent ui_object = Auth.GetUI(activity);
            activity.StartActivity(ui_object);
        }

        private void Auth_Complted(object sender, AuthenticatorCompletedEventArgs e)
        {
            if (e.IsAuthenticated)
            {
                AuthSuccess?.Invoke(this, e);
            }
            else
            {
                AuthFailure?.Invoke(this, e);
            }
        }
    }
}

Looks familiar? This is almost the same as iOS but we have Intend and activity instead of RootViewController. What is Intent? Click here!

How do I get ClientId and RedirectId? See my previous post here!

For this moment you can use the same values from GoogleService-Info.plist

Ok let's try it!

Again as on iOS, after successful login we're stuck at Google's page. As you probably figured our we need to consume redirect URL. For that we need separated Android Activity.

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using System;
using Xamarin.Forms;

namespace GC.SgnlR.Droid
{
    [Activity(Label = "ActivityCustomUrlSchemeInterceptor", NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
    [
        IntentFilter
        (
            actions: new[] { Intent.ActionView },
            Categories = new[]
                    {
                    Intent.CategoryDefault,
                    Intent.CategoryBrowsable
                    },
            DataSchemes = new[]
                    {
                        "REVERSED_CLIENT_ID",
                    },

            DataPath = "/oauth2redirect"
        )
    ]
    public class ActivityCustomUrlSchemeInterceptor : Activity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            global::Android.Net.Uri uri_android = Intent.Data;

            var googleAuth = DependencyService.Resolve<IGoogleAuth>();
            Uri uri_netfx = new Uri(uri_android.ToString());
            googleAuth.Auth.OnPageLoading(uri_netfx);

            this.Finish();
        }
    }
}
Create ActivityCustomUrlSchemeInterceptor.cs in Android project

If you're curious what's Activity Intent Filter πŸ€” you can check it out here! I think that "REVERSED_CLIENT_ID" is self-explanatory πŸ˜‰.

This Activity basically will catch redirect Url and pass it's data to our OAuth2Authenticator object in GoogleAuth class.

Right now we have to handle how to close Web browser in our app. I figured out this solution:

        private void Auth_Complted(object sender, AuthenticatorCompletedEventArgs e)
        {
            // Close browser 
            var activity = Xamarin.Essentials.Platform.CurrentActivity;
            var intent = new Intent(activity, typeof(MainActivity));
            intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
            activity.StartActivity(intent);

            if (e.IsAuthenticated)
            {
                AuthSuccess?.Invoke(this, e);
            }
            else
            {
                AuthFailure?.Invoke(this, e);
            }
        }
GoogleAuth.cs
πŸ’‘
ClearTop flag is very important here. This will assure that we'll retrieve existing Activity "MainActivity" from back stack and we'll clear all the Activities that are on the top of it.

You can dig into topic here:

Android Intent.FLAG_ACTIVITY_SINGLE_TOP AND Intent.FLAG_ACTIVITY_CLEAR_TOP
I have an app that I have running a media player and I want to resume the activity from my apps home activity. I can successfully do this by adding the following flags to the startActivity Call:

Right now login should work like charm! 🌟 We should be able to open Web view in app, login using our Google Account and be redirected back to the app.

I'm very happy with the outcome, we created consisted Interface for Google's login both for iOS and Android πŸŽ‰

IFirebaseService

Back in the day, we introduced this interface

using System.Threading.Tasks;

namespace GC.SignalR
{
    public interface IFirebaseService
    {
        Task<string> LoginWithCredentials(string idToken, string accessToken);
    }
}

This interface as has one purpose:

Exchange Google's IdToken and AccessToken for Firebase IdToken.

Firebase Init

To implement mentioned interface we have to add our app in Firebase Console and initialize it inside Android MainActivity. Β So let's log to our Firebase -> Click plus icon and select Android -> fill all needed info.

Download google-services.json file and put it into Android project.

πŸ’‘
Set google-services.json file Build action as GoogleServicesJson!

Next Add Xamarin.Firebase.Auth NuGet to the same project.

After adding this NuGet please Init Firebase in OnCreate method in your MainAcitvity

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            FirebaseApp.InitializeApp(this);

            DependencyService.Register<IGoogleAuth, GoogleAuth>();
            DependencyService.Register<IFirebaseService, FirebaseService>();

            LoadApplication(new App());
        }
MainActivity.cs in Android project

As you can see, we also registered FirebaseService.

IFirebaseService for πŸ€–

Now it's time to write some implementations

    internal class FirebaseService : IFirebaseService
    {
        public async Task<string> LoginWithCredentials(string idToken, string accessToken)
        {
            var authCredential = GoogleAuthProvider.GetCredential(idToken, accessToken);
            var auth = await FirebaseAuth.Instance.SignInWithCredentialAsync(authCredential);
            var result = await auth.User.GetIdToken(true).ToAwaitableTask();
            return (result as GetTokenResult)?.Token;
        }
    }

I'm using here some Extension Method ToAwaitableTask() that I found useful while working with Android.Gms.Task. Here you can see code and blog post! You can also find it in my repo. πŸ˜€

πŸ’‘
Update 13.04.2022
There is build-in Extension method AsAsync<TResult>()!

Testing

Lucky for us, navigation and messaging page is ready (this is beauty of cross platform development✨). So there is nothing left for development. Let's find out if it's working!

Launch Server -> Login -> Type message -> Press Send ⚑

Outcome:

What can I say more? πŸ˜‚

Conclusion

Developing consistent abstraction for Firebase Auth using Google is not easy cake but it can be done. For sure there are plenty of other possible solutions but maybe you'll get inspired... Who knows πŸ˜‰. Regarding this Part I'm very surprised that there was no ambush bug 😱 (as it was for iOS).

Final words

If you enjoyed this blog post, don't hesitate and Subscribe! I'll publish every 1-2 weeks about Xamarin, .NET and mobile development, if you're interested give it a try! (I won't spam or sell your e-mail πŸ˜…)

Cheers! 🍹

Show Comments