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

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

💡
This is first part of one huge blog post! For Part 2: Android click here!

tl dr; give me repo? Ok 😎

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.

Introduction

In this blogpost I'll describe using SignalR with Firebase Authentication and Google as a provider. I've seen a lot of blog posts about Firebase and SignalR but they covered only simple e-mail/password authentication.

I assume that you already have your Firebase project up and running, and Google Authentication provider is added.

Let's start with the server

Program.cs

First of all we have to create ASP.NET Core 6 project.
Let's use ASP.NET Core Empty. We're welcomed with minimalistic .NET 6 Program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

There is very usefull NuGet Package for using Firebase Auth in ASP.NET Core. So let's add it using our NuGet Package manager:

AspNetCore.Firebase.Authentication consist only of 2 extension methods so it's also good idea to copy it directly to your code base and get rid of this one dependency.

Now let's add SignalR and FirebaseAuthentication to our services altogether with Authentication and Authorization middleware, so our Program.cs will look like:

using AspNetCore.Firebase.Authentication.Extensions;
using ViralBlog.Server;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();
builder.Services.AddFirebaseAuthentication("https://securetoken.google.com/your-project-name", "your-project-name");

var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.MapHub<MessagingHub>("/messaging");

app.UseAuthentication();
app.UseAuthorization();

app.Run();

Need for Hub

Our next step is to create SignalR Hub. What is Hub? Click here to learn more.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

namespace ViralBlog.Server;

[Authorize]
public class MessagingHub : Hub
{
    public void SendMessage(string message) => Console.WriteLine(message);
}
💡
We've already added Hub in the Program.cs

Server side? Done.


Xamarin App

Take off

Create Xamarin app project Mobile App (Xamarin.Forms) and select blank template. By default iOS and Android projects are selected, keep it that way.

Login Page

Let's start with simple Login page

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="GC.SgnlR.MainPage">

    <Grid RowDefinitions="auto, *" BackgroundColor="#85C8DB">
        <Frame Grid.Row="0" 
               BackgroundColor="#4B799A" 
               VerticalOptions="StartAndExpand">
            <Label Text="Welcome to Git Commit!" 
                   HorizontalTextAlignment="Center" 
                   TextColor="White" 
                   FontSize="36"/>
        </Frame>
        <Button Grid.Row="1"
                Padding="16"
                CornerRadius="16" 
                VerticalOptions="Center"
                Clicked="Button_Clicked"
                Text="Login with Google" 
                BackgroundColor="#C3A5C0"/>
    </Grid>

</ContentPage>

Preview:

Ok this needs more refinement... Luckily for me I know amazing UX/UI designer that helped me with this, simple but looks great! Thanks Klaudia Keil!

This obviously looks so much better 🔥 If you want code just look it up in repo.

IGoogleAuth

We'll need abstraction that will be used in shared code layer to Authenticate via Google.

using System;
using Xamarin.Auth;

namespace GC.SignalR
{
    public interface IGoogleAuth
    {
        OAuth2Authenticator Auth { get; }
        event EventHandler<AuthenticatorCompletedEventArgs> AuthSuccess;
        event EventHandler<AuthenticatorCompletedEventArgs> AuthFailure;
        void LoginAsync();
    }
}
Place this interface in shared project

To utilize OAuth2Authenticator class you'll have to add this NuGet:

It's taken care by Microsoft and has 1.8M downloads. For me it's pretty legit. Add it to shared project and Android and iOS as well.

iOS

We have to implement interface mentioned above in the iOS project

using System;
using UIKit;
using Xamarin.Auth;

namespace GC.SignalR.iOS
{
    public class GoogleAuth : IGoogleAuth
    {
        public event EventHandler<AuthenticatorCompletedEventArgs> AuthSuccess;
        public event EventHandler<AuthenticatorCompletedEventArgs> AuthFailure;

        public OAuth2Authenticator Auth { get; private set; }

        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 vc = UIApplication.SharedApplication.KeyWindow.RootViewController;
            vc.PresentViewController(Auth.GetUI(), true, null);
        }

        private void Auth_Complted(object sender, AuthenticatorCompletedEventArgs e)
        {
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);

            if (e.IsAuthenticated)
            {
                AuthSuccess?.Invoke(this, e);
            }
            else
            {
                AuthFailure?.Invoke(this, e);
            }
        }
    }
}
Place this class in iOS project

Ok so you probably wonder... How do I get ClientId and RedirectUrl?

Easy, just go to your Firebase project main screen -> Add app -> iOS -> fill needed properties and register app -> Download config file.

Add this file to iOS project as it is shown in the Firebase. In this file you'll be able to find ClientId, your RedirectUrl is: "REVERSED_CLIENT_ID:/oauth2redirect"

💡
While you're adding GoogleService-Info.plist to your project, mark it as "BundleResource" and "Copy if newer" in it's properties 

Register our service in AppDelegate.FinishedLaunching method

DependencyService.Register<IGoogleAuth, GoogleAuth>();

Back to the MainPage.cs but code behind, we want to Login after clicking the button

using System;
using Xamarin.Auth;
using Xamarin.Forms;

namespace GC.SignalR
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            
                        var googleAuth = DependencyService.Resolve<IGoogleAuth>();
            googleAuth.AuthSuccess += GoogleAuth_AuthSuccess;
            googleAuth.AuthFailure += GoogleAuth_AuthFailure;
        }

        private void Button_Clicked(object sender, EventArgs e)
        {
            googleAuth.LoginAsync();
        }

        private void GoogleAuth_AuthFailure(object sender, AuthenticatorCompletedEventArgs e)
        {
            DisplayAlert("Login Failure!", null, "Ok");
        }

        private async void GoogleAuth_AuthSuccess(object sender, AuthenticatorCompletedEventArgs e)
        {
            await DisplayAlert("Success!", null, "Ok");
        }
    }
}

Okaaay! So let's try it!

Okay okay... after pressing Login button we see Google's login page. Yay! But after login we're stuck at Google's homepage and after clicking Done we're said that Auth failed. Why?

Our app doesn't know how to utilize redirect Url scheme, let's fix that!

We have to add custom scheme to our Info.plist. Double click on Info.plist -> Advanced -> URL Types -> Add URL Type.

Identifier: I used my Bundle Identifier
URL Schemes: use REVERSED_CLIENT_ID from GoogleService-Info.plist
Role: Viewer

next override AppDelegate.OpenUrl

        public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
        {
            // Convert iOS NSUrl to C#/netxf/BCL System.Uri - common API
            Uri uri_netfx = new Uri(url.AbsoluteString);

            var googleAuth = DependencyService.Resolve<IGoogleAuth>();

            // load redirect_url Page for parsing
            googleAuth.Auth.OnPageLoading(uri_netfx);

            return true;
        }
AppDelegate.cs

Rebuild, deploy... Login... and? It works! After successful login we're redirected back to our app.  

Firebase Login

Currently we have Google's IdToken and AccessToken, but we need Firebase IdToken. We need now Login to our Firebase.

Right now we'll add official Firebase bindings to our iOS project.

Quick abstraction:

using System.Threading.Tasks;

namespace GC.SignalR
{
    public interface IFirebaseService
    {
        Task<string> LoginWithCredentials(string tokenId, string accessToken);
    }
}
Add this class to shared project

Now implementation:

using Firebase.Auth;
using System.Threading.Tasks;

namespace GC.SignalR.iOS
{
    public class FirebaseService : IFirebaseService
    {
        public async Task<string> LoginWithCredentials(string idToken, string accessToken)
        {
            var authCredential = GoogleAuthProvider.GetCredential(idToken, accessToken);
            var auth = await Auth.DefaultInstance.SignInWithCredentialAsync(authCredential);
            return await auth.User.GetIdTokenAsync();
        }
    }
}
Add this class to iOS project

Remember to register the service, also we have to initialize Firebase 👆

Firebase.Core.App.Configure();
DependencyService.Register<IFirebaseService, FirebaseService>();
Add this lines to AppDelegate.FinishedLaunching method

Make use of our brand new service in Login Page's code behind. Change GoogleAuth_AuthSuccess method:

        private async void GoogleAuth_AuthSuccess(object sender, AuthenticatorCompletedEventArgs e)
        {
            var firebaseService = DependencyService.Resolve<IFirebaseService>();
            var token = await firebaseService.LoginWithCredentials(e.Account.Properties["id_token"], e.Account.Properties["access_token"]);

            await Navigation.PushAsync(new MessagingPage { BindingContext = token });   
        }
LoginPage.xaml.cs

As you can see we're already navigating to Messaging Page

Messaging Page

We'll do only refined version since I prefer my apps looking good 😎

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
             x:Class="GC.SgnlR.MessagingPage"
             BackgroundImageSource="Background">
    <ContentPage.Content>
        <Frame CornerRadius="20"
               HeightRequest="292"
               WidthRequest="308"
               VerticalOptions="Center"
               HorizontalOptions="Center"
               BackgroundColor="#F0F6F8">
            <Grid Padding="44,18" RowDefinitions="*,*,*">
                <Label Grid.Row="0" 
                       Text="Succesful login!"
                       VerticalOptions="Center"
                       FontSize="26"
                       FontAttributes="Bold"
                       HorizontalTextAlignment="Center"
                       TextColor="#000000"/>
                <Entry Grid.Row="1"
                       x:Name="entry" 
                       HeightRequest="44"
                       VerticalOptions="Center"
                       BackgroundColor="#FFFFFF" 
                       TextColor="Black"/>
                <Button Grid.Row="2"
                        Clicked="Button_Clicked"
                        VerticalOptions="Center"
                        Text="Send message"
                        TextColor="#FFFFFF"
                        FontSize="16"
                        FontAttributes="Bold"
                        BackgroundColor="#0C3D67" 
                        CornerRadius="15"
                        HeightRequest="47"
                        WidthRequest="133"
                        HorizontalOptions="Center"
                        xct:ShadowEffect.OffsetY="3"
                        xct:ShadowEffect.Color="#000000"
                        xct:ShadowEffect.Opacity="0.16"
                        xct:ShadowEffect.Radius="6"/>

            </Grid>
        </Frame>
    </ContentPage.Content>
</ContentPage>

Preview:

SignalR Time

To use SignalR we obviously need to add SignalR NuGet package

Add this NuGet to shared project

Messaging Page code behind

using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Net.Http;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace GC.SignalR
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MessagingPage : ContentPage
    {
        private string _token;
        private HubConnection _connection;

        public MessagingPage()
        {
            InitializeComponent();
        }

        private async void Button_Clicked(object sender, EventArgs e)
        {
            await _connection.InvokeCoreAsync("SendMessage", args: new[] { entry.Text });
        }

        protected async override void OnAppearing()
        {
            _token = BindingContext as string;
            _connection = new HubConnectionBuilder()
                .WithUrl("https://192.168.0.136:45455/messaging", options =>
                {
                    options.AccessTokenProvider = () => Task.FromResult(_token);
                    options.HttpMessageHandlerFactory = (message) =>
                    {
                        if (message is HttpClientHandler clientHandler)
                            // bypass SSL certificate
                            clientHandler.ServerCertificateCustomValidationCallback +=
                                                    (sender, certificate, chain, sslPolicyErrors) => { return true; };
                        return message;
                    };
                    options.Headers.Add("Authorization", _token);
                })
                .Build();

            await _connection.StartAsync();

            base.OnAppearing();
        }
    }
}

So... are we there yet?

Currently iOS + SignalR + Visual Studio 2022 has some nasty bug that will occur right away when you'll try to connect to your SignalR's Hub:

Exception caught Method not found: Microsoft.AspNetCore.Http.Connections.NegotiationResponse on iOS
The code seems to work with UWP in Xamarin.Forms but when i try this with iOS i get the following error Exception caught Method not found:Microsoft.AspNetCore.Http.Connections.NegotiationResponse

You can read about bug's current status here:

Visual Studio Feedback
Visual Studio Feedback

I can confirm that solution by adding this code to the iOS's csproj works

<ItemGroup>
	<PackageReference Include="System.Memory" Version="4.5.4">
		<IncludeAssets>none</IncludeAssets>
	</PackageReference>
	<PackageReference Include="System.Buffers" Version="4.5.1">
		<IncludeAssets>none</IncludeAssets>
	</PackageReference>
</ItemGroup>

Do I need to add it by hand? Yes.
Does these packages need separated ItemGroup? Yes.

FINALLY

Rebuild, deploy, login, run local SignalR server, et voila!

💡
Use Visual Studio's plugin "Conveyor by Keyoti" for accessing your web applications from other machines in local network

Cheers! I hope you learned something! 🎉 Subscribe for part 2!

Show Comments